@pipelex/mthds-form 0.11.0 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-E2IVBAW3.js → chunk-2D4NEFUH.js} +293 -104
- package/dist/chunk-2D4NEFUH.js.map +1 -0
- package/dist/chunk-7KQXGG6J.js +127 -0
- package/dist/chunk-7KQXGG6J.js.map +1 -0
- package/dist/{chunk-TID7ZN63.js → chunk-CM4KVG53.js} +13 -10
- package/dist/chunk-CM4KVG53.js.map +1 -0
- package/dist/{chunk-4JQAJT7C.js → chunk-RRTI7AIR.js} +25 -38
- package/dist/chunk-RRTI7AIR.js.map +1 -0
- package/dist/core/index.d.ts +2 -1
- package/dist/core/index.js +3 -2
- package/dist/core/native-content.d.ts +6 -1
- package/dist/core/native-content.js +1 -1
- package/dist/core/save-plan.d.ts +145 -0
- package/dist/core/save-plan.js +6 -0
- package/dist/core/save-plan.js.map +1 -0
- package/dist/core/stuff-files.d.ts +26 -3
- package/dist/core/stuff-files.js +2 -2
- package/dist/generative/index.js +7 -5
- package/dist/generative/index.js.map +1 -1
- package/dist/react/index.d.ts +177 -108
- package/dist/react/index.js +4 -3
- package/package.json +1 -1
- package/dist/chunk-4JQAJT7C.js.map +0 -1
- package/dist/chunk-E2IVBAW3.js.map +0 -1
- package/dist/chunk-TID7ZN63.js.map +0 -1
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { collectStuffFiles } from './chunk-RRTI7AIR.js';
|
|
2
|
+
import { viewableUrl, readDataUrl, dataUrlExtensions } from './chunk-CM4KVG53.js';
|
|
3
|
+
|
|
4
|
+
// src/core/save-plan.ts
|
|
5
|
+
function planStuffSave(field, value, options) {
|
|
6
|
+
const found = collectStuffFiles(field, value);
|
|
7
|
+
const files = [];
|
|
8
|
+
const unavailable = [];
|
|
9
|
+
for (const file of found) {
|
|
10
|
+
const planned = planFileSave(file, options);
|
|
11
|
+
if (planned) files.push(planned);
|
|
12
|
+
else
|
|
13
|
+
unavailable.push({ name: nameFor(file, options.baseName), kind: file.kind, path: file.path });
|
|
14
|
+
}
|
|
15
|
+
const isBareFile = found.length === 1 && found[0]?.path === field.name;
|
|
16
|
+
if (isBareFile && files.length === 1) return { files, unavailable };
|
|
17
|
+
const jsonName = `${safeName(options.baseName) || "result"}.json`;
|
|
18
|
+
return {
|
|
19
|
+
files: [
|
|
20
|
+
...distinctNames(files, jsonName),
|
|
21
|
+
{
|
|
22
|
+
name: jsonName,
|
|
23
|
+
mimeType: "application/json",
|
|
24
|
+
kind: "data",
|
|
25
|
+
path: field.name,
|
|
26
|
+
text: jsonCopy(value)
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
unavailable
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function distinctNames(files, reserved) {
|
|
33
|
+
const taken = /* @__PURE__ */ new Set([reserved.toLowerCase()]);
|
|
34
|
+
return files.map((file) => {
|
|
35
|
+
let name = file.name;
|
|
36
|
+
for (let count = 2; taken.has(name.toLowerCase()); count += 1) {
|
|
37
|
+
name = numbered(file.name, count);
|
|
38
|
+
}
|
|
39
|
+
taken.add(name.toLowerCase());
|
|
40
|
+
return name === file.name ? file : { ...file, name };
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
function numbered(name, count) {
|
|
44
|
+
const extension = extensionOf(name);
|
|
45
|
+
return extension ? `${name.slice(0, -(extension.length + 1))}-${count}.${name.slice(-extension.length)}` : `${name}-${count}`;
|
|
46
|
+
}
|
|
47
|
+
function planFileSave(file, options) {
|
|
48
|
+
const common = { kind: file.kind, path: file.path };
|
|
49
|
+
if (file.text !== void 0) {
|
|
50
|
+
return {
|
|
51
|
+
...common,
|
|
52
|
+
name: nameFor(file, options.baseName),
|
|
53
|
+
mimeType: "text/html",
|
|
54
|
+
text: file.text
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
const url = viewableUrl(options.resolveUrl?.(file.url ?? "")) ?? viewableUrl(file.publicUrl) ?? viewableUrl(file.url);
|
|
58
|
+
if (!url) return void 0;
|
|
59
|
+
const encoded = readDataUrl(url);
|
|
60
|
+
if (encoded) {
|
|
61
|
+
return {
|
|
62
|
+
...common,
|
|
63
|
+
name: nameFor(file, options.baseName, dataUrlExtensions(encoded.mediaType)),
|
|
64
|
+
mimeType: encoded.mediaType,
|
|
65
|
+
url
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const name = nameFor(file, options.baseName);
|
|
69
|
+
return { ...common, name, mimeType: file.mimeType ?? typeForName(name), url };
|
|
70
|
+
}
|
|
71
|
+
var TYPE_FOR_EXTENSION = {
|
|
72
|
+
png: "image/png",
|
|
73
|
+
jpg: "image/jpeg",
|
|
74
|
+
jpeg: "image/jpeg",
|
|
75
|
+
gif: "image/gif",
|
|
76
|
+
webp: "image/webp",
|
|
77
|
+
avif: "image/avif",
|
|
78
|
+
svg: "image/svg+xml",
|
|
79
|
+
pdf: "application/pdf",
|
|
80
|
+
html: "text/html"
|
|
81
|
+
};
|
|
82
|
+
function typeForName(name) {
|
|
83
|
+
const extension = name.match(/\.([A-Za-z0-9]{1,8})$/)?.[1]?.toLowerCase();
|
|
84
|
+
return extension && TYPE_FOR_EXTENSION[extension] || "application/octet-stream";
|
|
85
|
+
}
|
|
86
|
+
function nameFor(file, base, allowed) {
|
|
87
|
+
const own = file.filename ? safeName(file.filename) : "";
|
|
88
|
+
const place = file.path.split(".").slice(1).join("-");
|
|
89
|
+
const named = safeName(base) || "result";
|
|
90
|
+
const stem = own || (place ? `${named}-${place}` : named);
|
|
91
|
+
const stated = own ? extensionOf(own) : "";
|
|
92
|
+
if (allowed) {
|
|
93
|
+
if (stated && allowed.includes(stated)) return stem;
|
|
94
|
+
const bare = stated ? stem.slice(0, -(stated.length + 1)) : stem;
|
|
95
|
+
const extension2 = allowed[0];
|
|
96
|
+
return extension2 ? `${bare}.${extension2}` : bare;
|
|
97
|
+
}
|
|
98
|
+
if (own) return own;
|
|
99
|
+
const extension = file.extension ?? extensionOf(pathOf(file.url ?? ""));
|
|
100
|
+
return extension ? `${stem}.${extension}` : stem;
|
|
101
|
+
}
|
|
102
|
+
function extensionOf(name) {
|
|
103
|
+
return name.match(/\.([A-Za-z0-9]{1,8})$/)?.[1]?.toLowerCase() ?? "";
|
|
104
|
+
}
|
|
105
|
+
function pathOf(url) {
|
|
106
|
+
return url.split(/[?#]/)[0] ?? url;
|
|
107
|
+
}
|
|
108
|
+
function safeName(name) {
|
|
109
|
+
const printable = Array.from(name).filter((character) => {
|
|
110
|
+
const code = character.charCodeAt(0);
|
|
111
|
+
return code > 31 && code !== 127;
|
|
112
|
+
}).join("");
|
|
113
|
+
const trimmed = (printable.split(/[/\\]/).pop() ?? "").trim();
|
|
114
|
+
return trimmed === "." || trimmed === ".." ? "" : trimmed;
|
|
115
|
+
}
|
|
116
|
+
function jsonCopy(value) {
|
|
117
|
+
const text = JSON.stringify(
|
|
118
|
+
value,
|
|
119
|
+
(_key, member) => typeof member === "bigint" ? member.toString() : member,
|
|
120
|
+
2
|
|
121
|
+
);
|
|
122
|
+
return text ?? "null";
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export { planFileSave, planStuffSave };
|
|
126
|
+
//# sourceMappingURL=chunk-7KQXGG6J.js.map
|
|
127
|
+
//# sourceMappingURL=chunk-7KQXGG6J.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/save-plan.ts"],"names":["extension"],"mappings":";;;;AAsIO,SAAS,aAAA,CAAc,KAAA,EAAiB,KAAA,EAAgB,OAAA,EAAoC;AACjG,EAAA,MAAM,KAAA,GAAQ,iBAAA,CAAkB,KAAA,EAAO,KAAK,CAAA;AAC5C,EAAA,MAAM,QAAoB,EAAC;AAC3B,EAAA,MAAM,cAAiC,EAAC;AACxC,EAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACxB,IAAA,MAAM,OAAA,GAAU,YAAA,CAAa,IAAA,EAAM,OAAO,CAAA;AAC1C,IAAA,IAAI,OAAA,EAAS,KAAA,CAAM,IAAA,CAAK,OAAO,CAAA;AAAA;AAE7B,MAAA,WAAA,CAAY,IAAA,CAAK,EAAE,IAAA,EAAM,OAAA,CAAQ,MAAM,OAAA,CAAQ,QAAQ,CAAA,EAAG,IAAA,EAAM,IAAA,CAAK,IAAA,EAAM,IAAA,EAAM,IAAA,CAAK,MAAM,CAAA;AAAA,EAChG;AAKA,EAAA,MAAM,UAAA,GAAa,MAAM,MAAA,KAAW,CAAA,IAAK,MAAM,CAAC,CAAA,EAAG,SAAS,KAAA,CAAM,IAAA;AAClE,EAAA,IAAI,cAAc,KAAA,CAAM,MAAA,KAAW,GAAG,OAAO,EAAE,OAAO,WAAA,EAAY;AAElE,EAAA,MAAM,WAAW,CAAA,EAAG,QAAA,CAAS,OAAA,CAAQ,QAAQ,KAAK,QAAQ,CAAA,KAAA,CAAA;AAC1D,EAAA,OAAO;AAAA,IACL,KAAA,EAAO;AAAA,MACL,GAAG,aAAA,CAAc,KAAA,EAAO,QAAQ,CAAA;AAAA,MAChC;AAAA,QACE,IAAA,EAAM,QAAA;AAAA,QACN,QAAA,EAAU,kBAAA;AAAA,QACV,IAAA,EAAM,MAAA;AAAA,QACN,MAAM,KAAA,CAAM,IAAA;AAAA,QACZ,IAAA,EAAM,SAAS,KAAK;AAAA;AACtB,KACF;AAAA,IACA;AAAA,GACF;AACF;AAiBA,SAAS,aAAA,CAAc,OAA4B,QAAA,EAA8B;AAC/E,EAAA,MAAM,wBAAQ,IAAI,GAAA,CAAI,CAAC,QAAA,CAAS,WAAA,EAAa,CAAC,CAAA;AAC9C,EAAA,OAAO,KAAA,CAAM,GAAA,CAAI,CAAC,IAAA,KAAS;AACzB,IAAA,IAAI,OAAO,IAAA,CAAK,IAAA;AAChB,IAAA,KAAA,IAAS,KAAA,GAAQ,GAAG,KAAA,CAAM,GAAA,CAAI,KAAK,WAAA,EAAa,CAAA,EAAG,KAAA,IAAS,CAAA,EAAG;AAC7D,MAAA,IAAA,GAAO,QAAA,CAAS,IAAA,CAAK,IAAA,EAAM,KAAK,CAAA;AAAA,IAClC;AACA,IAAA,KAAA,CAAM,GAAA,CAAI,IAAA,CAAK,WAAA,EAAa,CAAA;AAC5B,IAAA,OAAO,SAAS,IAAA,CAAK,IAAA,GAAO,OAAO,EAAE,GAAG,MAAM,IAAA,EAAK;AAAA,EACrD,CAAC,CAAA;AACH;AAGA,SAAS,QAAA,CAAS,MAAc,KAAA,EAAuB;AACrD,EAAA,MAAM,SAAA,GAAY,YAAY,IAAI,CAAA;AAClC,EAAA,OAAO,SAAA,GACH,GAAG,IAAA,CAAK,KAAA,CAAM,GAAG,EAAE,SAAA,CAAU,MAAA,GAAS,CAAA,CAAE,CAAC,CAAA,CAAA,EAAI,KAAK,CAAA,CAAA,EAAI,IAAA,CAAK,KAAA,CAAM,CAAC,SAAA,CAAU,MAAM,CAAC,CAAA,CAAA,GACnF,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI,KAAK,CAAA,CAAA;AACtB;AAaO,SAAS,YAAA,CAAa,MAAiB,OAAA,EAAgD;AAC5F,EAAA,MAAM,SAAS,EAAE,IAAA,EAAM,KAAK,IAAA,EAAM,IAAA,EAAM,KAAK,IAAA,EAAK;AAClD,EAAA,IAAI,IAAA,CAAK,SAAS,MAAA,EAAW;AAC3B,IAAA,OAAO;AAAA,MACL,GAAG,MAAA;AAAA,MACH,IAAA,EAAM,OAAA,CAAQ,IAAA,EAAM,OAAA,CAAQ,QAAQ,CAAA;AAAA,MACpC,QAAA,EAAU,WAAA;AAAA,MACV,MAAM,IAAA,CAAK;AAAA,KACb;AAAA,EACF;AAKA,EAAA,MAAM,GAAA,GACJ,WAAA,CAAY,OAAA,CAAQ,UAAA,GAAa,KAAK,GAAA,IAAO,EAAE,CAAC,CAAA,IAChD,YAAY,IAAA,CAAK,SAAS,CAAA,IAC1B,WAAA,CAAY,KAAK,GAAG,CAAA;AACtB,EAAA,IAAI,CAAC,KAAK,OAAO,MAAA;AACjB,EAAA,MAAM,OAAA,GAAU,YAAY,GAAG,CAAA;AAC/B,EAAA,IAAI,OAAA,EAAS;AAKX,IAAA,OAAO;AAAA,MACL,GAAG,MAAA;AAAA,MACH,IAAA,EAAM,QAAQ,IAAA,EAAM,OAAA,CAAQ,UAAU,iBAAA,CAAkB,OAAA,CAAQ,SAAS,CAAC,CAAA;AAAA,MAC1E,UAAU,OAAA,CAAQ,SAAA;AAAA,MAClB;AAAA,KACF;AAAA,EACF;AACA,EAAA,MAAM,IAAA,GAAO,OAAA,CAAQ,IAAA,EAAM,OAAA,CAAQ,QAAQ,CAAA;AAC3C,EAAA,OAAO,EAAE,GAAG,MAAA,EAAQ,IAAA,EAAM,QAAA,EAAU,KAAK,QAAA,IAAY,WAAA,CAAY,IAAI,CAAA,EAAG,GAAA,EAAI;AAC9E;AAGA,IAAM,kBAAA,GAAuD;AAAA,EAC3D,GAAA,EAAK,WAAA;AAAA,EACL,GAAA,EAAK,YAAA;AAAA,EACL,IAAA,EAAM,YAAA;AAAA,EACN,GAAA,EAAK,WAAA;AAAA,EACL,IAAA,EAAM,YAAA;AAAA,EACN,IAAA,EAAM,YAAA;AAAA,EACN,GAAA,EAAK,eAAA;AAAA,EACL,GAAA,EAAK,iBAAA;AAAA,EACL,IAAA,EAAM;AACR,CAAA;AAEA,SAAS,YAAY,IAAA,EAAsB;AACzC,EAAA,MAAM,YAAY,IAAA,CAAK,KAAA,CAAM,uBAAuB,CAAA,GAAI,CAAC,GAAG,WAAA,EAAY;AACxE,EAAA,OAAQ,SAAA,IAAa,kBAAA,CAAmB,SAAS,CAAA,IAAM,0BAAA;AACzD;AAsBA,SAAS,OAAA,CAAQ,IAAA,EAAiB,IAAA,EAAc,OAAA,EAAqC;AACnF,EAAA,MAAM,MAAM,IAAA,CAAK,QAAA,GAAW,QAAA,CAAS,IAAA,CAAK,QAAQ,CAAA,GAAI,EAAA;AACtD,EAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,IAAA,CAAK,KAAA,CAAM,GAAG,EAAE,KAAA,CAAM,CAAC,CAAA,CAAE,IAAA,CAAK,GAAG,CAAA;AACpD,EAAA,MAAM,KAAA,GAAQ,QAAA,CAAS,IAAI,CAAA,IAAK,QAAA;AAChC,EAAA,MAAM,OAAO,GAAA,KAAQ,KAAA,GAAQ,GAAG,KAAK,CAAA,CAAA,EAAI,KAAK,CAAA,CAAA,GAAK,KAAA,CAAA;AACnD,EAAA,MAAM,MAAA,GAAS,GAAA,GAAM,WAAA,CAAY,GAAG,CAAA,GAAI,EAAA;AACxC,EAAA,IAAI,OAAA,EAAS;AACX,IAAA,IAAI,MAAA,IAAU,OAAA,CAAQ,QAAA,CAAS,MAAM,GAAG,OAAO,IAAA;AAC/C,IAAA,MAAM,IAAA,GAAO,SAAS,IAAA,CAAK,KAAA,CAAM,GAAG,EAAE,MAAA,CAAO,MAAA,GAAS,CAAA,CAAE,CAAA,GAAI,IAAA;AAC5D,IAAA,MAAMA,UAAAA,GAAY,QAAQ,CAAC,CAAA;AAC3B,IAAA,OAAOA,UAAAA,GAAY,CAAA,EAAG,IAAI,CAAA,CAAA,EAAIA,UAAS,CAAA,CAAA,GAAK,IAAA;AAAA,EAC9C;AACA,EAAA,IAAI,KAAK,OAAO,GAAA;AAChB,EAAA,MAAM,SAAA,GAAY,KAAK,SAAA,IAAa,WAAA,CAAY,OAAO,IAAA,CAAK,GAAA,IAAO,EAAE,CAAC,CAAA;AACtE,EAAA,OAAO,SAAA,GAAY,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI,SAAS,CAAA,CAAA,GAAK,IAAA;AAC9C;AAGA,SAAS,YAAY,IAAA,EAAsB;AACzC,EAAA,OAAO,KAAK,KAAA,CAAM,uBAAuB,IAAI,CAAC,CAAA,EAAG,aAAY,IAAK,EAAA;AACpE;AAGA,SAAS,OAAO,GAAA,EAAqB;AACnC,EAAA,OAAO,GAAA,CAAI,KAAA,CAAM,MAAM,CAAA,CAAE,CAAC,CAAA,IAAK,GAAA;AACjC;AAQA,SAAS,SAAS,IAAA,EAAsB;AACtC,EAAA,MAAM,YAAY,KAAA,CAAM,IAAA,CAAK,IAAI,CAAA,CAC9B,MAAA,CAAO,CAAC,SAAA,KAAc;AACrB,IAAA,MAAM,IAAA,GAAO,SAAA,CAAU,UAAA,CAAW,CAAC,CAAA;AACnC,IAAA,OAAO,IAAA,GAAO,MAAQ,IAAA,KAAS,GAAA;AAAA,EACjC,CAAC,CAAA,CACA,IAAA,CAAK,EAAE,CAAA;AACV,EAAA,MAAM,OAAA,GAAA,CAAW,UAAU,KAAA,CAAM,OAAO,EAAE,GAAA,EAAI,IAAK,IAAI,IAAA,EAAK;AAC5D,EAAA,OAAO,OAAA,KAAY,GAAA,IAAO,OAAA,KAAY,IAAA,GAAO,EAAA,GAAK,OAAA;AACpD;AAOA,SAAS,SAAS,KAAA,EAAwB;AACxC,EAAA,MAAM,OAAO,IAAA,CAAK,SAAA;AAAA,IAChB,KAAA;AAAA,IACA,CAAC,MAAM,MAAA,KAAqB,OAAO,WAAW,QAAA,GAAW,MAAA,CAAO,UAAS,GAAI,MAAA;AAAA,IAC7E;AAAA,GACF;AACA,EAAA,OAAO,IAAA,IAAQ,MAAA;AACjB","file":"chunk-7KQXGG6J.js","sourcesContent":["import { dataUrlExtensions, readDataUrl, viewableUrl } from './native-content';\nimport { collectStuffFiles, type StuffFile, type StuffFileKind } from './stuff-files';\nimport type { RunField } from './descriptor';\n\n/**\n * What saving a result MEANS, decided with no DOM: which files, what each is\n * called, what type it is, and where its bytes are.\n *\n * ## Why the plan is separate from the delivery\n *\n * Handing bytes to a reader is the part that depends on where the view runs.\n * An ordinary browser tab saves through an object URL and a clicked link; a\n * view inside a sandboxed frame is refused all of that and has to ask its host\n * to do it, typically by passing links and inline contents across a bridge that\n * fetches the links itself. What to save is the same answer in both places, so\n * it is computed once, here, and a delivery only has to carry it out. The\n * default browser delivery lives in `./react`; a host supplies its own through\n * `ResultEnvProvider`'s `saveFiles`. See docs/result-view.md § \"Saving a\n * result\".\n *\n * ## Why a planned file carries a URL rather than bytes\n *\n * A host's download bridge takes links and fetches them itself, which is also\n * what keeps a cross-origin fetch out of a frame that cannot make one. So a\n * stored file is planned as the URL the gate admitted, and only the default\n * browser delivery fetches it. Content the stuff carries inline (the JSON copy\n * of the data, an HTML page) is planned as its text.\n */\n\n/** What a planned file is: one of the stuff's own files, or the JSON copy of its data. */\nexport type SaveFileKind = StuffFileKind | 'data';\n\ninterface SaveFileCommon {\n /**\n * What to save it as: a bare file name, never a path. The file's own\n * `filename` when it states one, else the base name and the file's place\n * below the result's root (`report-figures-0-image.png`), or the base name\n * alone for a file that is the whole result (`output.png`). No two files in\n * one plan share a name, compared without case.\n */\n name: string;\n /**\n * The media type. For a `data:` URL it is the type the gate admitted; for any\n * other URL it is what the payload states, or what the name's extension\n * implies, or `application/octet-stream` when nothing says.\n */\n mimeType: string;\n kind: SaveFileKind;\n /** Where it sat in the result, dotted (`output.figures.0.image`); the root's name for the JSON copy. */\n path: string;\n}\n\n/**\n * One file to hand to the reader: exactly one of `url` and `text` is set.\n *\n * `url` is the string the URL gate returned, after the host's resolver was\n * asked first, so a delivery never acts on a member nothing judged. It may be a\n * root-relative path, which a planner with no document cannot resolve; the\n * result view makes one absolute against its own document before a delivery\n * sees it. `text` is content the result carries inline.\n */\nexport type SaveFile =\n | (SaveFileCommon & { url: string; text?: undefined })\n | (SaveFileCommon & { text: string; url?: undefined });\n\n/** A file the result holds that cannot be saved, because no URL the gate admits was found for it. */\nexport interface UnavailableFile {\n /** What it would have been saved as, so the reader can be told which one. */\n name: string;\n kind: StuffFileKind;\n path: string;\n}\n\n/** Everything saving a whole result involves. */\nexport interface SavePlan {\n /** What to deliver, in the order the result shows it, the JSON copy last. */\n files: SaveFile[];\n /**\n * What the result holds and cannot be delivered. The JSON copy still carries\n * each one's reference, so the reader keeps it even without the bytes.\n */\n unavailable: UnavailableFile[];\n}\n\n/** A file a delivery did not hand over, and why when it knows. */\nexport interface SaveFailure {\n file: SaveFile;\n reason?: string;\n}\n\n/**\n * What a delivery reports. Every planned file not listed in `failed` was\n * delivered, so an empty list means everything went out.\n */\nexport interface SaveResult {\n failed: SaveFailure[];\n}\n\n/**\n * How files reach the reader. The default saves them in the browser; a host\n * whose view runs in a sandboxed frame supplies one that asks the host to.\n *\n * It resolves rather than rejects when a file does not go out, and names that\n * file in `failed`, so the control that asked can tell the reader. A throw or\n * a rejection is read as every file having failed.\n */\nexport type SaveFiles = (files: readonly SaveFile[]) => Promise<SaveResult>;\n\nexport interface SavePlanOptions {\n /**\n * Names the whole stuff: the JSON copy is `<baseName>.json`, a file with no\n * name of its own below the root is `<baseName>-<place>`, and a file that is\n * the whole stuff is `<baseName>.<ext>`.\n */\n baseName: string;\n /** The same resolver the result view paints through, asked before the payload's own URLs. */\n resolveUrl?: (url: string) => string | undefined;\n}\n\n/**\n * Everything a stuff contains, planned for saving.\n *\n * The data goes down as JSON, and any image, document or HTML page inside it\n * goes down as ITSELF: a JSON file holding a URL to a picture is not the\n * picture, and a reader who saves a report with three attachments wants the\n * three attachments.\n *\n * The JSON copy is planned whenever the stuff is anything more than one file. A\n * result that IS one image has nothing left over once the image is saved, so\n * `{\"url\": …}` beside it would be a second file saying less than the first. That\n * holds only when the image can be saved: a bare file the gate refuses leaves\n * the JSON copy as the one thing the reader gets, because it still carries the\n * reference.\n */\nexport function planStuffSave(field: RunField, value: unknown, options: SavePlanOptions): SavePlan {\n const found = collectStuffFiles(field, value);\n const files: SaveFile[] = [];\n const unavailable: UnavailableFile[] = [];\n for (const file of found) {\n const planned = planFileSave(file, options);\n if (planned) files.push(planned);\n else\n unavailable.push({ name: nameFor(file, options.baseName), kind: file.kind, path: file.path });\n }\n\n // A stuff that is nothing BUT one file, read from the collector rather than\n // from `field.kind`: a `native.Html` node's kind is `object`, and the whole\n // stuff is still the page.\n const isBareFile = found.length === 1 && found[0]?.path === field.name;\n if (isBareFile && files.length === 1) return { files, unavailable };\n\n const jsonName = `${safeName(options.baseName) || 'result'}.json`;\n return {\n files: [\n ...distinctNames(files, jsonName),\n {\n name: jsonName,\n mimeType: 'application/json',\n kind: 'data',\n path: field.name,\n text: jsonCopy(value),\n },\n ],\n unavailable,\n };\n}\n\n/**\n * The plan's files with no two sharing a name, and none taking the JSON copy's.\n *\n * A name derived from a file's place is unique by construction, but a name the\n * payload states is not: two documents a method wrote as `scan.pdf`, or one it\n * called `report.json` beside the JSON copy of `report`. A browser suffixes a\n * duplicate download itself, but a host's delivery may write the names as given,\n * into a folder or an archive, where the second file silently replaces the\n * first. So the first file keeps its name and each later one gets a number\n * before its extension (`scan-2.pdf`), compared without case, as the file\n * systems readers mostly use compare them.\n *\n * That is the whole plan's concern: a file's own button saves one file, which\n * collides with nothing, under its own name.\n */\nfunction distinctNames(files: readonly SaveFile[], reserved: string): SaveFile[] {\n const taken = new Set([reserved.toLowerCase()]);\n return files.map((file) => {\n let name = file.name;\n for (let count = 2; taken.has(name.toLowerCase()); count += 1) {\n name = numbered(file.name, count);\n }\n taken.add(name.toLowerCase());\n return name === file.name ? file : { ...file, name };\n });\n}\n\n/** `scan.pdf` as `scan-2.pdf`: the number before the extension, so the type still reads. */\nfunction numbered(name: string, count: number): string {\n const extension = extensionOf(name);\n return extension\n ? `${name.slice(0, -(extension.length + 1))}-${count}.${name.slice(-extension.length)}`\n : `${name}-${count}`;\n}\n\n/**\n * One file, planned on its own — what a file's own download button saves.\n *\n * The file's `path` starts at the root's name, as `collectStuffFiles` builds\n * it: the name leaves that first segment out, because the base name already\n * stands for the root.\n *\n * `undefined` when the file cannot be saved: a stored reference the host's\n * resolver does not answer and no URL the gate admits beside it. The button is\n * not drawn then, so a reader is never offered a download that cannot happen.\n */\nexport function planFileSave(file: StuffFile, options: SavePlanOptions): SaveFile | undefined {\n const common = { kind: file.kind, path: file.path };\n if (file.text !== undefined) {\n return {\n ...common,\n name: nameFor(file, options.baseName),\n mimeType: 'text/html',\n text: file.text,\n };\n }\n // Judged by the same gate the result view paints through, in the same\n // durability order: the host's resolver over the durable reference first,\n // then `public_url`, then the reference itself. What the gate returns is what\n // is planned, never the raw member.\n const url =\n viewableUrl(options.resolveUrl?.(file.url ?? '')) ??\n viewableUrl(file.publicUrl) ??\n viewableUrl(file.url);\n if (!url) return undefined;\n const encoded = readDataUrl(url);\n if (encoded) {\n // A `data:` URL is its own file, and the gate admitted it for its declared\n // type alone. That type names it: the payload's `filename` and the tail of\n // the payload are both the payload's say, and a `data:image/png` URL saved\n // under `image.html` is a page that runs when the reader opens it.\n return {\n ...common,\n name: nameFor(file, options.baseName, dataUrlExtensions(encoded.mediaType)),\n mimeType: encoded.mediaType,\n url,\n };\n }\n const name = nameFor(file, options.baseName);\n return { ...common, name, mimeType: file.mimeType ?? typeForName(name), url };\n}\n\n/** The type a name's extension implies, for a stored file whose payload states none. */\nconst TYPE_FOR_EXTENSION: Readonly<Record<string, string>> = {\n png: 'image/png',\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n gif: 'image/gif',\n webp: 'image/webp',\n avif: 'image/avif',\n svg: 'image/svg+xml',\n pdf: 'application/pdf',\n html: 'text/html',\n};\n\nfunction typeForName(name: string): string {\n const extension = name.match(/\\.([A-Za-z0-9]{1,8})$/)?.[1]?.toLowerCase();\n return (extension && TYPE_FOR_EXTENSION[extension]) || 'application/octet-stream';\n}\n\n/**\n * What a saved file is called.\n *\n * The file's own `filename` wins when it has one: it is the name the producer\n * chose and the reader recognises. Otherwise the base name and the file's place\n * below the root name it (`report-attachments-2`), which is unlovely but\n * unambiguous, since two images from one result must not both land as\n * `image.png` and overwrite each other in the download folder.\n *\n * The path's first segment is left out because it is always the root's name,\n * and the base name already stands for the root, as it does in `<base>.json`:\n * keeping it would save the one image a pipe outputs as `output-output.png`. A\n * file that IS the root has no place below it and is named by the base name\n * alone. Every segment is a field's name or an index, neither of which holds a\n * dot, so splitting on dots recovers them.\n *\n * `allowed` is set for a `data:` URL, whose admitted type is the only thing\n * allowed to decide the extension: a name carrying one of those extensions\n * keeps it, and any other extension is replaced by the type's own.\n */\nfunction nameFor(file: StuffFile, base: string, allowed?: readonly string[]): string {\n const own = file.filename ? safeName(file.filename) : '';\n const place = file.path.split('.').slice(1).join('-');\n const named = safeName(base) || 'result';\n const stem = own || (place ? `${named}-${place}` : named);\n const stated = own ? extensionOf(own) : '';\n if (allowed) {\n if (stated && allowed.includes(stated)) return stem;\n const bare = stated ? stem.slice(0, -(stated.length + 1)) : stem;\n const extension = allowed[0];\n return extension ? `${bare}.${extension}` : bare;\n }\n if (own) return own;\n const extension = file.extension ?? extensionOf(pathOf(file.url ?? ''));\n return extension ? `${stem}.${extension}` : stem;\n}\n\n/** The extension a name ends in, lowercased, or none. Never guessed from the bytes. */\nfunction extensionOf(name: string): string {\n return name.match(/\\.([A-Za-z0-9]{1,8})$/)?.[1]?.toLowerCase() ?? '';\n}\n\n/** A URL without its query and fragment, so `x.png?sig=…` still ends in `.png`. */\nfunction pathOf(url: string): string {\n return url.split(/[?#]/)[0] ?? url;\n}\n\n/**\n * A name safe to hand a delivery: its last path segment, without control\n * characters, trimmed. A browser sanitises a download name itself, but a host's\n * bridge may write it to disk as given, and a payload's `filename` is the\n * payload's say. Empty when nothing is left.\n */\nfunction safeName(name: string): string {\n const printable = Array.from(name)\n .filter((character) => {\n const code = character.charCodeAt(0);\n return code > 0x1f && code !== 0x7f;\n })\n .join('');\n const trimmed = (printable.split(/[/\\\\]/).pop() ?? '').trim();\n return trimmed === '.' || trimmed === '..' ? '' : trimmed;\n}\n\n/**\n * The data as JSON, the way the JSON view shows it. A BigInt, which a JSON\n * parser configured to produce one can hand over, is written as its digits\n * rather than failing the whole save.\n */\nfunction jsonCopy(value: unknown): string {\n const text = JSON.stringify(\n value,\n (_key, member: unknown) => (typeof member === 'bigint' ? member.toString() : member),\n 2,\n );\n return text ?? 'null';\n}\n"]}
|
|
@@ -83,14 +83,17 @@ function readDateContent(value) {
|
|
|
83
83
|
function formatDateContent(content) {
|
|
84
84
|
return content.time ? `${content.date} ${content.time}` : content.date;
|
|
85
85
|
}
|
|
86
|
-
var VIEWABLE_DATA_MEDIA_TYPES = /* @__PURE__ */ new
|
|
87
|
-
"image/png",
|
|
88
|
-
"image/jpeg",
|
|
89
|
-
"image/gif",
|
|
90
|
-
"image/webp",
|
|
91
|
-
"image/avif",
|
|
92
|
-
"application/pdf"
|
|
86
|
+
var VIEWABLE_DATA_MEDIA_TYPES = /* @__PURE__ */ new Map([
|
|
87
|
+
["image/png", ["png"]],
|
|
88
|
+
["image/jpeg", ["jpg", "jpeg"]],
|
|
89
|
+
["image/gif", ["gif"]],
|
|
90
|
+
["image/webp", ["webp"]],
|
|
91
|
+
["image/avif", ["avif"]],
|
|
92
|
+
["application/pdf", ["pdf"]]
|
|
93
93
|
]);
|
|
94
|
+
function dataUrlExtensions(mediaType) {
|
|
95
|
+
return VIEWABLE_DATA_MEDIA_TYPES.get(mediaType) ?? [];
|
|
96
|
+
}
|
|
94
97
|
var RELATIVE_SENTINEL_ORIGIN = "https://url-gate.invalid";
|
|
95
98
|
var OWN_SCHEME_RE = /^[a-z][a-z0-9+.-]*:/i;
|
|
96
99
|
function stripUrlWhitespace(candidate) {
|
|
@@ -193,6 +196,6 @@ function hasTypedScalarMarkers(value) {
|
|
|
193
196
|
return isRecord(value) && TYPED_SCALAR_MARKERS.every((marker) => hasOwnProp(value, marker));
|
|
194
197
|
}
|
|
195
198
|
|
|
196
|
-
export { NATIVE_COMPOSITE_CONCEPT_REF, NATIVE_DATE_CONCEPT_REF, NATIVE_HTML_CONCEPT_REF, TYPED_SCALAR_MARKERS, formatDateContent, hasTypedScalarMarkers, isNativeCompositeNode, isNativeDateNode, isNativeHtmlNode, isNativeValueNode, isViewableUrl, readCompositeContent, readDataUrl, readDateContent, readDocumentContent, readHtmlContent, readImageContent, viewableUrl };
|
|
197
|
-
//# sourceMappingURL=chunk-
|
|
198
|
-
//# sourceMappingURL=chunk-
|
|
199
|
+
export { NATIVE_COMPOSITE_CONCEPT_REF, NATIVE_DATE_CONCEPT_REF, NATIVE_HTML_CONCEPT_REF, TYPED_SCALAR_MARKERS, dataUrlExtensions, formatDateContent, hasTypedScalarMarkers, isNativeCompositeNode, isNativeDateNode, isNativeHtmlNode, isNativeValueNode, isViewableUrl, readCompositeContent, readDataUrl, readDateContent, readDocumentContent, readHtmlContent, readImageContent, viewableUrl };
|
|
200
|
+
//# sourceMappingURL=chunk-CM4KVG53.js.map
|
|
201
|
+
//# sourceMappingURL=chunk-CM4KVG53.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/native-content.ts"],"names":["parsed"],"mappings":"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","file":"chunk-CM4KVG53.js","sourcesContent":["/**\n * How to READ the native content models — the result side's twin of\n * `./file-formats`.\n *\n * A renderer handed a result knows the field's `kind` from the descriptor, and\n * for the kinds whose value is a structured content model rather than a bare\n * scalar (`document`, `image`, `date`) it must then read that model's members.\n * **That is reading the standard, not sniffing at it.** The distinction is the\n * whole reason this module exists as data rather than as heuristics inside a\n * control: a reader keyed by a STATED kind consults a pinned definition, where\n * a reader that works out *which* kind it is holding by counting properties is\n * guessing — and guessing is exactly what [../../docs/derivation-swap.md]\n * records removing from the input side.\n *\n * ## Where each shape comes from\n *\n * `native.Image` and `native.Document` are pinned by the standard, in\n * `docs/spec/native-concepts.md` § \"The Pinned Set (MTHDS 1.0.0)\". Both carry a\n * required `url` — a storage URI, an HTTP(S) URL, or a base64 data URL — and\n * optional presentation members beside it. Nothing here adds a member the spec\n * does not state.\n *\n * `native.Date` is pinned the same way (`{date, time}`), but a `date` FIELD\n * inside a structured concept is not a `native.Date` value: it is a python\n * `datetime.date`, and the serialization layer stamps it with its class. So a\n * date arrives in one of three legal shapes, all three of which a run really\n * produces:\n *\n * \"2026-03-14\" a plain ISO string\n * { date: \"2026-03-14\", __class__: \"date\", … } a typed scalar\n * { date: \"2026-03-14\", time: \"15:40:00\" } native.Date\n *\n * The typed-scalar envelope is the one shape the standard does NOT pin — it is\n * a serialization fact, measured off a real run rather than read off a spec —\n * and it is called out as such here so nobody mistakes it for normative.\n *\n * This module lives in core, not beside the control that renders it, for the\n * reason `file-formats.ts` established: a host that shows a result its own way\n * needs the same answer the control uses, and two copies of an answer is two\n * places for it to drift. Pure data and pure functions — no React.\n */\n\nimport { hasOwnProp, ownProp } from './own-property';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/** A member the content models declare as `type = \"text\"`, when it is one. */\nfunction readText(record: Record<string, unknown>, key: string): string | undefined {\n const value = ownProp(record, key);\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\nfunction readNumber(record: Record<string, unknown>, key: string): number | undefined {\n const value = ownProp(record, key);\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n}\n\n/**\n * `native.Document`, read.\n *\n * `url` is the only required member; everything else is a producer's courtesy,\n * so a reader that demands any of them describes a value the standard permits\n * as malformed.\n */\nexport interface DocumentContentView {\n url: string;\n publicUrl?: string;\n mimeType?: string;\n filename?: string;\n title?: string;\n snippet?: string;\n}\n\n/** `native.Image`, read. `width`/`height` are optional but PAIRED by the spec. */\nexport interface ImageContentView {\n url: string;\n publicUrl?: string;\n caption?: string;\n mimeType?: string;\n filename?: string;\n width?: number;\n height?: number;\n sourcePrompt?: string;\n}\n\n/** `native.Date` (and the typed-scalar envelope a nested `date` field arrives in). */\nexport interface DateContentView {\n /** ISO 8601 calendar date, `YYYY-MM-DD`. */\n date: string;\n /** ISO 8601 time of day, present only when the source stated one. */\n time?: string;\n}\n\n/**\n * Read a `document`-kind value. `undefined` when the value carries no `url`,\n * which is the one member the standard requires — a caller renders its own\n * absence rather than being handed a half-value to test.\n */\nexport function readDocumentContent(value: unknown): DocumentContentView | undefined {\n if (!isRecord(value)) return undefined;\n const url = readText(value, 'url');\n if (url === undefined) return undefined;\n return {\n url,\n publicUrl: readText(value, 'public_url'),\n mimeType: readText(value, 'mime_type'),\n filename: readText(value, 'filename'),\n title: readText(value, 'title'),\n snippet: readText(value, 'snippet'),\n };\n}\n\n/** Read an `image`-kind value. Same contract as `readDocumentContent`. */\nexport function readImageContent(value: unknown): ImageContentView | undefined {\n if (!isRecord(value)) return undefined;\n const url = readText(value, 'url');\n if (url === undefined) return undefined;\n return {\n url,\n publicUrl: readText(value, 'public_url'),\n caption: readText(value, 'caption'),\n mimeType: readText(value, 'mime_type'),\n filename: readText(value, 'filename'),\n width: readNumber(value, 'width'),\n height: readNumber(value, 'height'),\n sourcePrompt: readText(value, 'source_prompt'),\n };\n}\n\n/**\n * `native.Html`, read.\n *\n * The only content model here that is NOT reached by a field kind, and the\n * reason is a deliberate decision of the standard: the kind vocabulary has no\n * `html` member, so a `native.Html` value is an `object` node over its two\n * declared fields. That is right for the descriptor — the kinds name how a value\n * is ENTERED, and markup is entered as text — and wrong for a result view, which\n * would otherwise print a page's source at a reader.\n *\n * So this one is keyed by CONCEPT rather than by kind, through\n * `isNativeHtmlNode` below. That is a membership test on facts the descriptor\n * states (`concept_ref`, `refines`), which the standard's own page calls out as\n * the supported way to ask \"does this refine `native.X`?\" — the opposite of\n * inspecting a value to guess what it is.\n */\nexport interface HtmlContentView {\n /** The markup itself. The one required member. */\n innerHtml: string;\n /** A class name for a wrapper a consumer may put around it, when stated. */\n cssClass?: string;\n}\n\n/** Read an `native.Html` value. `undefined` when it carries no markup. */\nexport function readHtmlContent(value: unknown): HtmlContentView | undefined {\n if (!isRecord(value)) return undefined;\n const innerHtml = readText(value, 'inner_html');\n if (innerHtml === undefined) return undefined;\n return { innerHtml, cssClass: readText(value, 'css_class') };\n}\n\n/** The concept refs whose content model is multi-property, spelled once each. */\nexport const NATIVE_HTML_CONCEPT_REF = 'native.Html';\nexport const NATIVE_DATE_CONCEPT_REF = 'native.Date';\nexport const NATIVE_COMPOSITE_CONCEPT_REF = 'native.Composite';\n\n/** One member of a composite: the name it was composed under, and its content. */\nexport interface CompositeMember {\n name: string;\n value: unknown;\n}\n\n/**\n * Read a `native.Composite` — a named composition of contents.\n *\n * The members are returned in the order the payload states them, which is the\n * order the method composed them in and the only order there is: a composite\n * declares no fields, so there is no authored order to prefer over it.\n *\n * `undefined` for anything that is not a record, so the caller falls back.\n */\nexport function readCompositeContent(value: unknown): CompositeMember[] | undefined {\n if (!isRecord(value)) return undefined;\n return Object.entries(value).map(([name, member]) => ({ name, value: member }));\n}\n\n/**\n * Whether a descriptor node carries markup — its concept IS `native.Html`, or\n * refines it.\n *\n * The refinement chain is checked, not just the leaf, because a method may\n * declare `legal.ClauseMarkup` refining `native.Html` and a reader of that result\n * wants the markup rendered just the same. `refines` is on the wire for exactly\n * this question.\n */\nexport function isNativeHtmlNode(node: NativeNode): boolean {\n return refinesNative(node, NATIVE_HTML_CONCEPT_REF);\n}\n\n/**\n * Whether a node IS a `native.Date` — the other content model the kind\n * vocabulary cannot name.\n *\n * `native.Date` is `{date, time}`, two properties, so nothing unwraps it and its\n * node is an `object`. Correct for the descriptor and wrong for a reader: a date\n * result rendered structurally is a two-field card whose second field says \"not\n * provided\", where the answer is a date. The `date` ARM already reads exactly\n * this model — `readDateContent` takes `{date, time}` — it just never fires,\n * because the kind says `object`.\n *\n * A `date` FIELD inside a structure is a different thing and needs none of this:\n * it carries `kind: \"date\"` already.\n */\nexport function isNativeDateNode(node: NativeNode): boolean {\n return refinesNative(node, NATIVE_DATE_CONCEPT_REF);\n}\n\n/**\n * Whether a node is a `native.Composite` — a named bag of contents.\n *\n * The third concept the kind vocabulary cannot name, and the one where it is\n * least able to: a composite declares no members at all, so its descriptor is\n * `kind: \"unknown\"` and its payload schema is `{additionalProperties: true}`\n * with no properties. Both are TRUE — the standard's escape hatch, honestly\n * used — and both leave a renderer with nothing to read, so the default\n * fallback prints the whole thing as a JSON blob.\n *\n * That is the worst available answer to a value that is, by definition, a map\n * of ordinary `StuffContent`s. Keyed by concept, a renderer can at least show\n * the members as the named things they are. See `CompositeValue`.\n */\nexport function isNativeCompositeNode(node: NativeNode): boolean {\n return refinesNative(node, NATIVE_COMPOSITE_CONCEPT_REF);\n}\n\n/**\n * Whether a node is one of the three natives the kind vocabulary cannot name —\n * a date, a page, a composite — which a renderer reads by CONCEPT before it\n * reads the kind. A list of one answers no (the predicates refuse a `list`\n * node), and its item answers yes.\n *\n * One answer, read twice: the result view renders a list whose item answers yes\n * one value per line rather than as a table, and the generative layer delegates\n * that list to the kernel, because no catalog component reads a native value.\n * Not re-exported: a host asks the three predicates it needs.\n */\nexport function isNativeValueNode(node: NativeNode): boolean {\n return isNativeDateNode(node) || isNativeHtmlNode(node) || isNativeCompositeNode(node);\n}\n\n/** What the native predicates read off a node: its stated kind and its concept. */\ninterface NativeNode {\n kind?: string;\n conceptRef?: string;\n refines?: readonly string[];\n}\n\n/**\n * The node IS one of the named native, or of a concept refining it.\n *\n * **A list of them is not one of them.** Plurality is not on the concept: a\n * plural node's `concept_ref` is its ELEMENT's, with the multiplicity stripped,\n * so a `native.Date[]` result is a `list` node whose concept reads\n * `native.Date`. Asking the concept alone answered yes for the whole list, and\n * every caller then treated the list as ONE date, one page or one composite: a\n * list of dates rendered as a single absent date, and a list of pages collected\n * no page at all. So a node whose stated kind is `list`\n * answers no, and its `item` answers for itself. The kind is the descriptor's\n * own statement, so this reads a stated fact and inspects no value.\n */\nfunction refinesNative(node: NativeNode, ref: string): boolean {\n if (node.kind === 'list') return false;\n if (node.conceptRef === ref) return true;\n return node.refines?.includes(ref) ?? false;\n}\n\n/**\n * Read a `date`-kind value, in any of the three shapes a run produces.\n *\n * The `date` member is read one level deep, because `native.Date`'s own `date`\n * member is a `datetime.date` and therefore arrives inside the same typed-scalar\n * envelope the whole value may already be wrapped in. One level is enough and\n * the recursion is bounded on purpose: a second envelope would be a\n * serialization bug, and following it forever would turn one into a hang.\n */\nexport function readDateContent(value: unknown): DateContentView | undefined {\n if (typeof value === 'string') {\n return value.length > 0 ? { date: value } : undefined;\n }\n if (!isRecord(value)) return undefined;\n\n const inner = ownProp(value, 'date');\n const date =\n typeof inner === 'string' ? inner : isRecord(inner) ? readText(inner, 'date') : undefined;\n if (date === undefined) return undefined;\n return { date, time: readText(value, 'time') };\n}\n\n/** `{date}` / `{date, time}` as one line, which is how both are read aloud. */\nexport function formatDateContent(content: DateContentView): string {\n return content.time ? `${content.date} ${content.time}` : content.date;\n}\n\n/**\n * The media types a `data:` URL may carry and still be viewable, each with the\n * extensions a saved file of that type may carry, the first being the one given\n * to a name that has none of them.\n *\n * An allow-list rather than a deny-list, because the question a sink asks is\n * \"can this paint without executing\", and only a closed set answers it. The\n * raster types are the ones the preview arms already name; `application/pdf` is\n * here because the input control previews a `data:application/pdf` value a\n * storage-less host wrote back, through an `<object>`, and reads this same\n * predicate to decide.\n *\n * `image/svg+xml` is deliberately absent: an SVG paints as a picture and\n * EXECUTES as a document the moment it is opened in a tab, which is one click\n * away from every image this package paints.\n *\n * The extensions live here, beside each admitted type, rather than in the save\n * planner that reads them: a type admitted without one would save under a name\n * with no extension, and the two lists used to be kept in step by hand.\n */\nconst VIEWABLE_DATA_MEDIA_TYPES: ReadonlyMap<string, readonly [string, ...string[]]> = new Map([\n ['image/png', ['png']],\n ['image/jpeg', ['jpg', 'jpeg']],\n ['image/gif', ['gif']],\n ['image/webp', ['webp']],\n ['image/avif', ['avif']],\n ['application/pdf', ['pdf']],\n]);\n\n/**\n * The extensions a saved `data:` file of this admitted media type may carry,\n * the first being its own. Empty for a type the gate does not admit.\n */\nexport function dataUrlExtensions(mediaType: string): readonly string[] {\n return VIEWABLE_DATA_MEDIA_TYPES.get(mediaType) ?? [];\n}\n\n/**\n * An origin no host can be, to resolve a relative reference against.\n *\n * `.invalid` is reserved by RFC 2606 and resolves nowhere, so nothing can be\n * fetched from it by accident. It exists only so a path can be parsed at all;\n * the answer taken from the parse is whether the reference STAYED on it.\n */\nconst RELATIVE_SENTINEL_ORIGIN = 'https://url-gate.invalid';\n\n/**\n * A scheme, by RFC 3986's grammar: a letter, then letters, digits, `+`, `-`, `.`\n *\n * Read off the string rather than inferred from a parse, because a parse needs a\n * base and the base is what makes the answer wrong — see {@link viewableUrl}.\n */\nconst OWN_SCHEME_RE = /^[a-z][a-z0-9+.-]*:/i;\n\n/**\n * What the WHATWG URL parser strips before it parses, applied before we judge.\n *\n * Leading and trailing C0 controls and spaces are removed, and every internal\n * tab, line feed and carriage return. This is not tidying: a scheme test run on\n * the raw string and a browser running on the parsed one disagree about\n * `\" https://cdn/x.png\"` — `new URL()` reads it as `https:` and `/^https?:/`\n * does not — and a host that validated a member by parsing while this gate\n * prefix-matched would have judged a different string than the one painted.\n */\nfunction stripUrlWhitespace(candidate: string): string {\n // Written out rather than as a character-class regex: the range IS the control\n // characters, which is exactly what a regex may not spell (`no-control-regex`).\n let start = 0;\n let end = candidate.length;\n while (start < end && candidate.charCodeAt(start) <= 0x20) start += 1;\n while (end > start && candidate.charCodeAt(end - 1) <= 0x20) end -= 1;\n // Scan before rebuilding, and rebuild only from the first character that has\n // to go. Almost every URL has nothing to strip inside it, and the one that\n // does is the one that is huge: an inline `data:` image is judged three or\n // four times per render, and a per-character rebuild of a megabyte-long\n // payload each time is hundreds of milliseconds of frozen main thread for a\n // string that comes back identical.\n for (let index = start; index < end; index += 1) {\n const code = candidate.charCodeAt(index);\n // Tab, line feed, carriage return - removed wherever they appear, not only\n // at the edges, which is what the parser does with them.\n if (code === 0x09 || code === 0x0a || code === 0x0d) {\n let stripped = candidate.slice(start, index);\n for (let rest = index; rest < end; rest += 1) {\n const restCode = candidate.charCodeAt(rest);\n if (restCode !== 0x09 && restCode !== 0x0a && restCode !== 0x0d)\n stripped += candidate[rest];\n }\n return stripped;\n }\n }\n return start === 0 && end === candidate.length ? candidate : candidate.slice(start, end);\n}\n\n/** What a `data:` URL says about the bytes it carries. */\nexport interface DataUrlView {\n /** Lowercased, without its parameters; `text/plain` when the URL declares none. */\n mediaType: string;\n /** The size of the DECODED payload, in bytes. */\n bytes: number;\n}\n\n/**\n * A `data:` URL's media type and decoded size, or `undefined` for any other URL.\n *\n * A host that encodes a picked file in the browser writes the file itself into\n * the value's `url`, so a thirty-kilobyte PDF is a forty-thousand-character\n * string, and printed where a reference would be it tells a person nothing. Its\n * type and its size are what they can read.\n *\n * The size is COUNTED rather than decoded. A base64 payload's size follows from\n * how many symbols it holds, and decoding a megabyte to measure it would be the\n * frozen main thread {@link stripUrlWhitespace} is written to avoid. This is a\n * reading for display, not a gate: whether the URL may be painted is still\n * {@link viewableUrl}'s answer alone.\n */\nexport function readDataUrl(candidate: string): DataUrlView | undefined {\n const url = candidate.trimStart();\n if (!/^data:/i.test(url)) return undefined;\n const comma = url.indexOf(',');\n if (comma === -1) return undefined;\n const [declared = '', ...parameters] = url.slice('data:'.length, comma).split(';');\n const trimmed = declared.trim().toLowerCase();\n // A fragment is not payload. `#page=2` on an inline PDF is the viewer's, and\n // counting it as bytes is counting the URL rather than the file.\n const fragment = url.indexOf('#', comma);\n const payload = fragment === -1 ? url.slice(comma + 1) : url.slice(comma + 1, fragment);\n const base64 = parameters.some((parameter) => parameter.trim().toLowerCase() === 'base64');\n return {\n mediaType: trimmed === '' ? 'text/plain' : trimmed,\n bytes: base64 ? base64Bytes(payload) : percentEncodedBytes(payload),\n };\n}\n\n/**\n * Three bytes for every four symbols, ignoring what is not one: the `=`\n * padding, and the line breaks a MIME encoder wraps its output at. Both\n * alphabets count, the standard one and the URL-safe one.\n *\n * A percent-escape is resolved to the character it stands for before it is\n * judged, because a payload may be escaped anywhere — `YQ%3D%3D` is `YQ==`,\n * one byte, and counting `3` and `D` as symbols made it four.\n */\nfunction base64Bytes(payload: string): number {\n let symbols = 0;\n for (let index = 0; index < payload.length; index += 1) {\n let code = payload.charCodeAt(index);\n if (code === 0x25) {\n const escaped = Number.parseInt(payload.slice(index + 1, index + 3), 16);\n if (!Number.isNaN(escaped) && index + 2 < payload.length) {\n code = escaped;\n index += 2;\n }\n }\n if (\n (code >= 0x41 && code <= 0x5a) ||\n (code >= 0x61 && code <= 0x7a) ||\n (code >= 0x30 && code <= 0x39) ||\n code === 0x2b ||\n code === 0x2f ||\n code === 0x2d ||\n code === 0x5f\n )\n symbols += 1;\n }\n return Math.floor((symbols * 3) / 4);\n}\n\n/** A percent-escape is one byte; any other character is its UTF-8 length. */\nfunction percentEncodedBytes(payload: string): number {\n const escapes = payload.match(/%[0-9a-f]{2}/gi)?.length ?? 0;\n return new TextEncoder().encode(payload).length - escapes * 2;\n}\n\n/** The media type a `data:` URL declares, lowercased; `text/plain` when it declares none. */\nfunction dataUrlMediaType(parsed: URL): string {\n const declared = parsed.pathname.split(/[;,]/, 1)[0] ?? '';\n const trimmed = declared.trim().toLowerCase();\n return trimmed === '' ? 'text/plain' : trimmed;\n}\n\n/**\n * The URL a browser can paint, normalised — or `undefined` when there is none.\n *\n * **The string this returns is the string a sink must use.** That is the whole\n * point of returning one rather than answering yes or no: the gate judges a\n * normalised URL, so handing the caller back the raw member would let the two\n * diverge again on exactly the whitespace the parser strips.\n *\n * The gate parses rather than prefix-matches, and branches on the protocol the\n * parser reports:\n *\n * - `http:` and `https:` are viewable.\n * - `blob:` is viewable. A blob URL is bound to the origin that minted it, so a\n * payload cannot forge one that points anywhere else.\n * - `data:` is viewable only for a media type in {@link VIEWABLE_DATA_MEDIA_TYPES}.\n * `data:text/html` is a document at the EMBEDDING page's own origin, which is\n * what made an unsandboxed frame over a payload URL a real hole.\n * - Everything else is not: `javascript:`, `file:` (a run on a developer's\n * machine writes one, and a browser refuses to load it from an http page) and\n * `pipelex-storage://`, which cannot be resolved client-side at all — that is\n * the upload seam's job (see [../../docs/upload-seam.md]), and a renderer with\n * no resolver must show the reference rather than a broken `<img>`.\n *\n * **A root-relative path counts**, and leaving it out was a real bug rather\n * than an omission. A host's URL resolver naturally hands back a path on its\n * OWN origin — `/api/assets/…`, an authenticated route that streams the object\n * — which is the whole point of resolving rather than exposing storage. This\n * predicate rejected it for having no scheme, so the resolved URL was thrown\n * away and the arms fell back to the payload's `public_url`: a presigned URL\n * that had already expired. The image stayed broken with the fix in place.\n *\n * A path is rejected when it is protocol-relative — `//host/x`, and the two\n * backslash spellings the parser reads the same way (`\\\\host\\x`, `/\\host/x`) —\n * and then checked against the sentinel as well. An accepted path is returned\n * relative, because making it absolute against a sentinel would point it at\n * nowhere.\n */\nexport function viewableUrl(candidate: string | undefined): string | undefined {\n if (!candidate) return undefined;\n const stripped = stripUrlWhitespace(candidate);\n if (stripped === '') return undefined;\n\n // Whether the string carries a scheme is read OFF THE STRING, before any base\n // is applied, because the parser reads a candidate whose scheme equals the\n // base's as relative to that base. Deciding it with a second, base-less parse\n // let the two disagree: `https:cdn.example/x.png` was judged absolute and\n // resolved against the sentinel, so the gate handed a sink\n // `https://url-gate.invalid/cdn.example/x.png` — an internal placeholder, in a\n // real `<img src>`, in place of a URL that had worked. The grammar is RFC\n // 3986's, and it agrees with the parser on every string either accepts.\n if (OWN_SCHEME_RE.test(stripped)) {\n let parsed: URL;\n try {\n parsed = new URL(stripped);\n } catch {\n return undefined;\n }\n switch (parsed.protocol) {\n case 'http:':\n case 'https:':\n case 'blob:':\n return parsed.href;\n case 'data:':\n return VIEWABLE_DATA_MEDIA_TYPES.has(dataUrlMediaType(parsed)) ? parsed.href : undefined;\n default:\n return undefined;\n }\n }\n\n // No scheme of its own: a reference relative to wherever the page is. Only a\n // root-relative path qualifies.\n if (!stripped.startsWith('/')) return undefined;\n // Protocol-relative, rejected on the SPELLING. \"Did it stay on the sentinel\"\n // reads as the same rule and is blind in exactly one place — the sentinel's\n // own host — so `//url-gate.invalid/x` passed the test written to stop it.\n const afterSlash = stripped.charCodeAt(1);\n if (afterSlash === 0x2f || afterSlash === 0x5c) return undefined;\n let parsed: URL;\n try {\n parsed = new URL(stripped, RELATIVE_SENTINEL_ORIGIN);\n } catch {\n return undefined;\n }\n return parsed.origin === RELATIVE_SENTINEL_ORIGIN ? stripped : undefined;\n}\n\n/**\n * Whether a browser can paint this URL as-is — {@link viewableUrl} as a guard.\n *\n * Prefer `viewableUrl` wherever the URL is then USED: this answers the question\n * without handing back the normalised string, so a caller that acts on the raw\n * candidate acts on a string the gate did not judge.\n */\nexport function isViewableUrl(url: string | undefined): url is string {\n return viewableUrl(url) !== undefined;\n}\n\n/**\n * The typed-scalar markers the serialization layer stamps onto a value.\n *\n * Exported so a host can recognise (and strip) them; nothing in the kernel\n * BRANCHES on them, which is the point — a reader keyed by these would be\n * reading the serializer instead of the standard.\n */\nexport const TYPED_SCALAR_MARKERS = ['__class__', '__module__'] as const;\n\n/** Whether a record carries the typed-scalar markers. Informational. */\nexport function hasTypedScalarMarkers(value: unknown): boolean {\n return isRecord(value) && TYPED_SCALAR_MARKERS.every((marker) => hasOwnProp(value, marker));\n}\n"]}
|
|
@@ -1,7 +1,23 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readHtmlContent, readImageContent, readDocumentContent, isNativeHtmlNode } from './chunk-CM4KVG53.js';
|
|
2
2
|
import { ownProp } from './chunk-V3OV2XQT.js';
|
|
3
3
|
|
|
4
4
|
// src/core/stuff-files.ts
|
|
5
|
+
function readStuffFile(kind, value, path) {
|
|
6
|
+
if (kind === "markup") {
|
|
7
|
+
const content2 = readHtmlContent(value);
|
|
8
|
+
return content2?.innerHtml ? { text: content2.innerHtml, extension: "html", path, kind } : void 0;
|
|
9
|
+
}
|
|
10
|
+
const content = kind === "image" ? readImageContent(value) : readDocumentContent(value);
|
|
11
|
+
if (!content?.url) return void 0;
|
|
12
|
+
return {
|
|
13
|
+
url: content.url,
|
|
14
|
+
publicUrl: content.publicUrl,
|
|
15
|
+
filename: content.filename,
|
|
16
|
+
mimeType: content.mimeType,
|
|
17
|
+
path,
|
|
18
|
+
kind
|
|
19
|
+
};
|
|
20
|
+
}
|
|
5
21
|
function collectStuffFiles(field, value) {
|
|
6
22
|
const found = [];
|
|
7
23
|
walk(field, value, [], found);
|
|
@@ -11,44 +27,15 @@ function walk(field, raw, path, out) {
|
|
|
11
27
|
const value = unwrapContent(field, raw);
|
|
12
28
|
const here = path.length > 0 ? path : [field.name];
|
|
13
29
|
if (isNativeHtmlNode(field)) {
|
|
14
|
-
const
|
|
15
|
-
if (
|
|
16
|
-
out.push({
|
|
17
|
-
text: content.innerHtml,
|
|
18
|
-
extension: "html",
|
|
19
|
-
path: here.join("."),
|
|
20
|
-
kind: "markup"
|
|
21
|
-
});
|
|
22
|
-
}
|
|
30
|
+
const file = readStuffFile("markup", value, here.join("."));
|
|
31
|
+
if (file) out.push(file);
|
|
23
32
|
return;
|
|
24
33
|
}
|
|
25
34
|
switch (field.kind) {
|
|
26
|
-
case "image":
|
|
27
|
-
const content = readImageContent(value);
|
|
28
|
-
if (content?.url) {
|
|
29
|
-
out.push({
|
|
30
|
-
url: content.url,
|
|
31
|
-
publicUrl: content.publicUrl,
|
|
32
|
-
filename: content.filename,
|
|
33
|
-
mimeType: content.mimeType,
|
|
34
|
-
path: here.join("."),
|
|
35
|
-
kind: "image"
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
35
|
+
case "image":
|
|
40
36
|
case "document": {
|
|
41
|
-
const
|
|
42
|
-
if (
|
|
43
|
-
out.push({
|
|
44
|
-
url: content.url,
|
|
45
|
-
publicUrl: content.publicUrl,
|
|
46
|
-
filename: content.filename,
|
|
47
|
-
mimeType: content.mimeType,
|
|
48
|
-
path: here.join("."),
|
|
49
|
-
kind: "document"
|
|
50
|
-
});
|
|
51
|
-
}
|
|
37
|
+
const file = readStuffFile(field.kind, value, here.join("."));
|
|
38
|
+
if (file) out.push(file);
|
|
52
39
|
return;
|
|
53
40
|
}
|
|
54
41
|
case "object": {
|
|
@@ -75,6 +62,6 @@ function unwrapContent(field, value) {
|
|
|
75
62
|
return unwrapped === void 0 ? value : unwrapped;
|
|
76
63
|
}
|
|
77
64
|
|
|
78
|
-
export { collectStuffFiles };
|
|
79
|
-
//# sourceMappingURL=chunk-
|
|
80
|
-
//# sourceMappingURL=chunk-
|
|
65
|
+
export { collectStuffFiles, readStuffFile };
|
|
66
|
+
//# sourceMappingURL=chunk-RRTI7AIR.js.map
|
|
67
|
+
//# sourceMappingURL=chunk-RRTI7AIR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/stuff-files.ts"],"names":["content"],"mappings":"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","file":"chunk-RRTI7AIR.js","sourcesContent":["import { ownProp } from './own-property';\nimport {\n isNativeHtmlNode,\n readDocumentContent,\n readHtmlContent,\n readImageContent,\n} from './native-content';\nimport type { RunField } from './descriptor';\n\n/**\n * One file a stuff carries: what to fetch, and what to call it once saved.\n */\nexport interface StuffFile {\n /**\n * The durable reference or URL the value states, for a file held BY\n * REFERENCE. Absent for inline content, which needs no fetch.\n */\n url?: string;\n /**\n * The content itself, for a file the stuff carries INLINE — markup, today.\n * Exactly one of `url` and `text` is set.\n */\n text?: string;\n /** The extension to save inline content under, without the dot. */\n extension?: string;\n /**\n * The URL that works outside this session, when the value carries one. A host\n * whose display URL is session-scoped (an owner-checked proxy route) needs\n * this for anything that leaves the browser.\n */\n publicUrl?: string;\n /** The file's own name when it states one — otherwise the caller derives it. */\n filename?: string;\n mimeType?: string;\n /**\n * Where it sat, dotted, starting at the root's name:\n * `report.attachments.2.scan`. A saved file with no name of its own is named\n * by the base name and the segments after the first (`report-attachments-2-scan`),\n * since the base name stands for the root, so a hand-built path must start at\n * the root's name too, or its first real segment is lost.\n */\n path: string;\n kind: StuffFileKind;\n}\n\n/**\n * What a file is: a picture, a document, or a page of markup the stuff carries\n * inline. It is the descriptor's statement (the node's kind, or its concept for\n * `native.Html`), never a reading of the bytes.\n */\nexport type StuffFileKind = 'image' | 'document' | 'markup';\n\n/**\n * One file value, read as the file it is declared to be — or `undefined` when\n * the value holds nothing to save (no URL, no markup).\n *\n * The walk below calls this for every file node it reaches, and the result view\n * calls it for the one file a control is drawn beside, so a file's own download\n * button and the whole-result download read the value the same way. `kind` is\n * the caller's statement from the descriptor; this never inspects the value to\n * decide what it is.\n */\nexport function readStuffFile(\n kind: StuffFileKind,\n value: unknown,\n path: string,\n): StuffFile | undefined {\n if (kind === 'markup') {\n const content = readHtmlContent(value);\n return content?.innerHtml\n ? { text: content.innerHtml, extension: 'html', path, kind }\n : undefined;\n }\n const content = kind === 'image' ? readImageContent(value) : readDocumentContent(value);\n if (!content?.url) return undefined;\n return {\n url: content.url,\n publicUrl: content.publicUrl,\n filename: content.filename,\n mimeType: content.mimeType,\n path,\n kind,\n };\n}\n\n/**\n * Every file inside a stuff, found by walking the DESCRIPTOR — never the value.\n *\n * The distinction is the whole point. A payload walk would have to decide what\n * a file is by looking at it: an object with a `url`, a string that ends in\n * `.pdf`. That is the guessing this package exists to remove, and it is wrong in\n * both directions — a method whose output legitimately has a `url` field of its\n * own is not a document, and a stored image whose reference carries no extension\n * is. The descriptor already states `kind: \"image\"` or `kind: \"document\"`, so\n * the walk reads the declaration and consults the value only for the members\n * that declaration promises.\n *\n * Ordering is the authored one, depth-first, which is the order the rendered\n * view puts them in — so a download of three images numbers them the way the\n * reader saw them.\n */\nexport function collectStuffFiles(field: RunField, value: unknown): StuffFile[] {\n const found: StuffFile[] = [];\n walk(field, value, [], found);\n return found;\n}\n\nfunction walk(field: RunField, raw: unknown, path: string[], out: StuffFile[]): void {\n const value = unwrapContent(field, raw);\n const here = path.length > 0 ? path : [field.name];\n\n // Markup, before the kind switch, exactly as the renderer takes it: a\n // `native.Html` node's KIND is `object` (its content model has two members),\n // so the switch below would walk into it looking for files and find none.\n //\n // It is a file even though nothing stores it as one. The stuff IS a page —\n // the reader is looking at a rendered report — and handing them a JSON\n // envelope with the page inside it as a string is the same mistake as handing\n // them a URL instead of a picture. `isNativeHtmlNode` asks the descriptor\n // whether the concept refines `native.Html`, which is the standard's own way\n // to ask; it never looks at the value to decide.\n if (isNativeHtmlNode(field)) {\n const file = readStuffFile('markup', value, here.join('.'));\n if (file) out.push(file);\n return;\n }\n\n switch (field.kind) {\n case 'image':\n case 'document': {\n const file = readStuffFile(field.kind, value, here.join('.'));\n if (file) out.push(file);\n return;\n }\n case 'object': {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return;\n const record = value as Record<string, unknown>;\n for (const child of field.fields) {\n walk(child, ownProp(record, child.name), [...here, child.name], out);\n }\n return;\n }\n case 'list': {\n if (!Array.isArray(value)) return;\n // The element descriptor's own `name` is unused by the standard, so the\n // index is what identifies an entry — the same thing the rendered list\n // shows beside each row.\n value.forEach((entry, index) => walk(field.item, entry, [...here, String(index)], out));\n return;\n }\n default:\n // Every other kind is a scalar. It cannot contain a file, and a `native`\n // node with no declared members (an `unknown` kind — a composite, say)\n // states nothing a walk could follow, so there is nothing to look at.\n return;\n }\n}\n\n/**\n * The one unwrap path, matching the renderer's: a content model is opened by\n * the property the schema named, and by nothing else.\n */\nfunction unwrapContent(field: RunField, value: unknown): unknown {\n if (!field.contentKey) return value;\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return value;\n const unwrapped = ownProp(value as Record<string, unknown>, field.contentKey);\n return unwrapped === undefined ? value : unwrapped;\n}\n"]}
|
package/dist/core/index.d.ts
CHANGED
|
@@ -2,7 +2,8 @@ export { BooleanRunField, ConceptCategory, DateRunField, EnumRunField, FileRunFi
|
|
|
2
2
|
export { IOMultiplicity, InputForm, InputFormTopLevelField, OutputForm, PipeIOContract, PipeIOContracts, PipeInputContract, PipeInputFormDescriptor, PipeOutputContract, PipeOutputFormDescriptor } from 'mthds/protocol';
|
|
3
3
|
export { buildResultField, buildRunFields, getPipeInputForm } from './derive.js';
|
|
4
4
|
export { getPipeOutputForm } from './output-form.js';
|
|
5
|
-
export { StuffFile, collectStuffFiles } from './stuff-files.js';
|
|
5
|
+
export { StuffFile, StuffFileKind, collectStuffFiles } from './stuff-files.js';
|
|
6
|
+
export { SaveFailure, SaveFile, SaveFileKind, SaveFiles, SavePlan, SavePlanOptions, SaveResult, UnavailableFile, planFileSave, planStuffSave } from './save-plan.js';
|
|
6
7
|
export { DOCUMENT_FORMATS, FileFormat, IMAGE_FORMATS, acceptLabel, acceptMap, formatsForKind, isAcceptedFile } from './file-formats.js';
|
|
7
8
|
export { narrowFileFormats } from './narrow-file-formats.js';
|
|
8
9
|
export { CompositeMember, DataUrlView, DateContentView, DocumentContentView, HtmlContentView, ImageContentView, NATIVE_COMPOSITE_CONCEPT_REF, NATIVE_DATE_CONCEPT_REF, NATIVE_HTML_CONCEPT_REF, TYPED_SCALAR_MARKERS, formatDateContent, hasTypedScalarMarkers, isNativeCompositeNode, isNativeDateNode, isNativeHtmlNode, isViewableUrl, readCompositeContent, readDataUrl, readDateContent, readDocumentContent, readHtmlContent, readImageContent, viewableUrl } from './native-content.js';
|
package/dist/core/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
export { describeValidationError, displayValue, valueAtProperty } from '../chunk-RPA7DNSK.js';
|
|
2
2
|
export { apiInputsFromRunValues, fieldsForContract, inputDataFromWorkingMemory, outputsFromPipeOutput, rjsfDataFromRunValues, runValuesFromStore, setValueAtPath, storeInputDataFromRunValues } from '../chunk-4KGQ65TQ.js';
|
|
3
|
-
export { collectStuffFiles } from '../chunk-4JQAJT7C.js';
|
|
4
3
|
export { narrowFileFormats } from '../chunk-CAKQ5VFT.js';
|
|
5
|
-
export { NATIVE_COMPOSITE_CONCEPT_REF, NATIVE_DATE_CONCEPT_REF, NATIVE_HTML_CONCEPT_REF, TYPED_SCALAR_MARKERS, formatDateContent, hasTypedScalarMarkers, isNativeCompositeNode, isNativeDateNode, isNativeHtmlNode, isViewableUrl, readCompositeContent, readDataUrl, readDateContent, readDocumentContent, readHtmlContent, readImageContent, viewableUrl } from '../chunk-TID7ZN63.js';
|
|
6
4
|
export { getPipeOutputForm } from '../chunk-DARLDVJM.js';
|
|
5
|
+
export { planFileSave, planStuffSave } from '../chunk-7KQXGG6J.js';
|
|
6
|
+
export { collectStuffFiles } from '../chunk-RRTI7AIR.js';
|
|
7
|
+
export { NATIVE_COMPOSITE_CONCEPT_REF, NATIVE_DATE_CONCEPT_REF, NATIVE_HTML_CONCEPT_REF, TYPED_SCALAR_MARKERS, formatDateContent, hasTypedScalarMarkers, isNativeCompositeNode, isNativeDateNode, isNativeHtmlNode, isViewableUrl, readCompositeContent, readDataUrl, readDateContent, readDocumentContent, readHtmlContent, readImageContent, viewableUrl } from '../chunk-CM4KVG53.js';
|
|
7
8
|
export { buildResultField, buildRunFields, getPipeInputForm } from '../chunk-ZB4RYUKV.js';
|
|
8
9
|
export { conceptCategory } from '../chunk-FABY3DEY.js';
|
|
9
10
|
export { DOCUMENT_FORMATS, IMAGE_FORMATS, acceptLabel, acceptMap, formatsForKind, isAcceptedFile } from '../chunk-XCO6GTEC.js';
|
|
@@ -193,6 +193,11 @@ interface NativeNode {
|
|
|
193
193
|
declare function readDateContent(value: unknown): DateContentView | undefined;
|
|
194
194
|
/** `{date}` / `{date, time}` as one line, which is how both are read aloud. */
|
|
195
195
|
declare function formatDateContent(content: DateContentView): string;
|
|
196
|
+
/**
|
|
197
|
+
* The extensions a saved `data:` file of this admitted media type may carry,
|
|
198
|
+
* the first being its own. Empty for a type the gate does not admit.
|
|
199
|
+
*/
|
|
200
|
+
declare function dataUrlExtensions(mediaType: string): readonly string[];
|
|
196
201
|
/** What a `data:` URL says about the bytes it carries. */
|
|
197
202
|
interface DataUrlView {
|
|
198
203
|
/** Lowercased, without its parameters; `text/plain` when the URL declares none. */
|
|
@@ -272,4 +277,4 @@ declare const TYPED_SCALAR_MARKERS: readonly ["__class__", "__module__"];
|
|
|
272
277
|
/** Whether a record carries the typed-scalar markers. Informational. */
|
|
273
278
|
declare function hasTypedScalarMarkers(value: unknown): boolean;
|
|
274
279
|
|
|
275
|
-
export { type CompositeMember, type DataUrlView, type DateContentView, type DocumentContentView, type HtmlContentView, type ImageContentView, NATIVE_COMPOSITE_CONCEPT_REF, NATIVE_DATE_CONCEPT_REF, NATIVE_HTML_CONCEPT_REF, TYPED_SCALAR_MARKERS, formatDateContent, hasTypedScalarMarkers, isNativeCompositeNode, isNativeDateNode, isNativeHtmlNode, isNativeValueNode, isViewableUrl, readCompositeContent, readDataUrl, readDateContent, readDocumentContent, readHtmlContent, readImageContent, viewableUrl };
|
|
280
|
+
export { type CompositeMember, type DataUrlView, type DateContentView, type DocumentContentView, type HtmlContentView, type ImageContentView, NATIVE_COMPOSITE_CONCEPT_REF, NATIVE_DATE_CONCEPT_REF, NATIVE_HTML_CONCEPT_REF, TYPED_SCALAR_MARKERS, dataUrlExtensions, formatDateContent, hasTypedScalarMarkers, isNativeCompositeNode, isNativeDateNode, isNativeHtmlNode, isNativeValueNode, isViewableUrl, readCompositeContent, readDataUrl, readDateContent, readDocumentContent, readHtmlContent, readImageContent, viewableUrl };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { NATIVE_COMPOSITE_CONCEPT_REF, NATIVE_DATE_CONCEPT_REF, NATIVE_HTML_CONCEPT_REF, TYPED_SCALAR_MARKERS, formatDateContent, hasTypedScalarMarkers, isNativeCompositeNode, isNativeDateNode, isNativeHtmlNode, isNativeValueNode, isViewableUrl, readCompositeContent, readDataUrl, readDateContent, readDocumentContent, readHtmlContent, readImageContent, viewableUrl } from '../chunk-
|
|
1
|
+
export { NATIVE_COMPOSITE_CONCEPT_REF, NATIVE_DATE_CONCEPT_REF, NATIVE_HTML_CONCEPT_REF, TYPED_SCALAR_MARKERS, dataUrlExtensions, formatDateContent, hasTypedScalarMarkers, isNativeCompositeNode, isNativeDateNode, isNativeHtmlNode, isNativeValueNode, isViewableUrl, readCompositeContent, readDataUrl, readDateContent, readDocumentContent, readHtmlContent, readImageContent, viewableUrl } from '../chunk-CM4KVG53.js';
|
|
2
2
|
import '../chunk-V3OV2XQT.js';
|
|
3
3
|
//# sourceMappingURL=native-content.js.map
|
|
4
4
|
//# sourceMappingURL=native-content.js.map
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { StuffFileKind, StuffFile } from './stuff-files.js';
|
|
2
|
+
import { RunField } from './descriptor.js';
|
|
3
|
+
import './contracts.js';
|
|
4
|
+
import 'mthds/protocol';
|
|
5
|
+
import './file-formats.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* What saving a result MEANS, decided with no DOM: which files, what each is
|
|
9
|
+
* called, what type it is, and where its bytes are.
|
|
10
|
+
*
|
|
11
|
+
* ## Why the plan is separate from the delivery
|
|
12
|
+
*
|
|
13
|
+
* Handing bytes to a reader is the part that depends on where the view runs.
|
|
14
|
+
* An ordinary browser tab saves through an object URL and a clicked link; a
|
|
15
|
+
* view inside a sandboxed frame is refused all of that and has to ask its host
|
|
16
|
+
* to do it, typically by passing links and inline contents across a bridge that
|
|
17
|
+
* fetches the links itself. What to save is the same answer in both places, so
|
|
18
|
+
* it is computed once, here, and a delivery only has to carry it out. The
|
|
19
|
+
* default browser delivery lives in `./react`; a host supplies its own through
|
|
20
|
+
* `ResultEnvProvider`'s `saveFiles`. See docs/result-view.md § "Saving a
|
|
21
|
+
* result".
|
|
22
|
+
*
|
|
23
|
+
* ## Why a planned file carries a URL rather than bytes
|
|
24
|
+
*
|
|
25
|
+
* A host's download bridge takes links and fetches them itself, which is also
|
|
26
|
+
* what keeps a cross-origin fetch out of a frame that cannot make one. So a
|
|
27
|
+
* stored file is planned as the URL the gate admitted, and only the default
|
|
28
|
+
* browser delivery fetches it. Content the stuff carries inline (the JSON copy
|
|
29
|
+
* of the data, an HTML page) is planned as its text.
|
|
30
|
+
*/
|
|
31
|
+
/** What a planned file is: one of the stuff's own files, or the JSON copy of its data. */
|
|
32
|
+
type SaveFileKind = StuffFileKind | 'data';
|
|
33
|
+
interface SaveFileCommon {
|
|
34
|
+
/**
|
|
35
|
+
* What to save it as: a bare file name, never a path. The file's own
|
|
36
|
+
* `filename` when it states one, else the base name and the file's place
|
|
37
|
+
* below the result's root (`report-figures-0-image.png`), or the base name
|
|
38
|
+
* alone for a file that is the whole result (`output.png`). No two files in
|
|
39
|
+
* one plan share a name, compared without case.
|
|
40
|
+
*/
|
|
41
|
+
name: string;
|
|
42
|
+
/**
|
|
43
|
+
* The media type. For a `data:` URL it is the type the gate admitted; for any
|
|
44
|
+
* other URL it is what the payload states, or what the name's extension
|
|
45
|
+
* implies, or `application/octet-stream` when nothing says.
|
|
46
|
+
*/
|
|
47
|
+
mimeType: string;
|
|
48
|
+
kind: SaveFileKind;
|
|
49
|
+
/** Where it sat in the result, dotted (`output.figures.0.image`); the root's name for the JSON copy. */
|
|
50
|
+
path: string;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* One file to hand to the reader: exactly one of `url` and `text` is set.
|
|
54
|
+
*
|
|
55
|
+
* `url` is the string the URL gate returned, after the host's resolver was
|
|
56
|
+
* asked first, so a delivery never acts on a member nothing judged. It may be a
|
|
57
|
+
* root-relative path, which a planner with no document cannot resolve; the
|
|
58
|
+
* result view makes one absolute against its own document before a delivery
|
|
59
|
+
* sees it. `text` is content the result carries inline.
|
|
60
|
+
*/
|
|
61
|
+
type SaveFile = (SaveFileCommon & {
|
|
62
|
+
url: string;
|
|
63
|
+
text?: undefined;
|
|
64
|
+
}) | (SaveFileCommon & {
|
|
65
|
+
text: string;
|
|
66
|
+
url?: undefined;
|
|
67
|
+
});
|
|
68
|
+
/** A file the result holds that cannot be saved, because no URL the gate admits was found for it. */
|
|
69
|
+
interface UnavailableFile {
|
|
70
|
+
/** What it would have been saved as, so the reader can be told which one. */
|
|
71
|
+
name: string;
|
|
72
|
+
kind: StuffFileKind;
|
|
73
|
+
path: string;
|
|
74
|
+
}
|
|
75
|
+
/** Everything saving a whole result involves. */
|
|
76
|
+
interface SavePlan {
|
|
77
|
+
/** What to deliver, in the order the result shows it, the JSON copy last. */
|
|
78
|
+
files: SaveFile[];
|
|
79
|
+
/**
|
|
80
|
+
* What the result holds and cannot be delivered. The JSON copy still carries
|
|
81
|
+
* each one's reference, so the reader keeps it even without the bytes.
|
|
82
|
+
*/
|
|
83
|
+
unavailable: UnavailableFile[];
|
|
84
|
+
}
|
|
85
|
+
/** A file a delivery did not hand over, and why when it knows. */
|
|
86
|
+
interface SaveFailure {
|
|
87
|
+
file: SaveFile;
|
|
88
|
+
reason?: string;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* What a delivery reports. Every planned file not listed in `failed` was
|
|
92
|
+
* delivered, so an empty list means everything went out.
|
|
93
|
+
*/
|
|
94
|
+
interface SaveResult {
|
|
95
|
+
failed: SaveFailure[];
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* How files reach the reader. The default saves them in the browser; a host
|
|
99
|
+
* whose view runs in a sandboxed frame supplies one that asks the host to.
|
|
100
|
+
*
|
|
101
|
+
* It resolves rather than rejects when a file does not go out, and names that
|
|
102
|
+
* file in `failed`, so the control that asked can tell the reader. A throw or
|
|
103
|
+
* a rejection is read as every file having failed.
|
|
104
|
+
*/
|
|
105
|
+
type SaveFiles = (files: readonly SaveFile[]) => Promise<SaveResult>;
|
|
106
|
+
interface SavePlanOptions {
|
|
107
|
+
/**
|
|
108
|
+
* Names the whole stuff: the JSON copy is `<baseName>.json`, a file with no
|
|
109
|
+
* name of its own below the root is `<baseName>-<place>`, and a file that is
|
|
110
|
+
* the whole stuff is `<baseName>.<ext>`.
|
|
111
|
+
*/
|
|
112
|
+
baseName: string;
|
|
113
|
+
/** The same resolver the result view paints through, asked before the payload's own URLs. */
|
|
114
|
+
resolveUrl?: (url: string) => string | undefined;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Everything a stuff contains, planned for saving.
|
|
118
|
+
*
|
|
119
|
+
* The data goes down as JSON, and any image, document or HTML page inside it
|
|
120
|
+
* goes down as ITSELF: a JSON file holding a URL to a picture is not the
|
|
121
|
+
* picture, and a reader who saves a report with three attachments wants the
|
|
122
|
+
* three attachments.
|
|
123
|
+
*
|
|
124
|
+
* The JSON copy is planned whenever the stuff is anything more than one file. A
|
|
125
|
+
* result that IS one image has nothing left over once the image is saved, so
|
|
126
|
+
* `{"url": …}` beside it would be a second file saying less than the first. That
|
|
127
|
+
* holds only when the image can be saved: a bare file the gate refuses leaves
|
|
128
|
+
* the JSON copy as the one thing the reader gets, because it still carries the
|
|
129
|
+
* reference.
|
|
130
|
+
*/
|
|
131
|
+
declare function planStuffSave(field: RunField, value: unknown, options: SavePlanOptions): SavePlan;
|
|
132
|
+
/**
|
|
133
|
+
* One file, planned on its own — what a file's own download button saves.
|
|
134
|
+
*
|
|
135
|
+
* The file's `path` starts at the root's name, as `collectStuffFiles` builds
|
|
136
|
+
* it: the name leaves that first segment out, because the base name already
|
|
137
|
+
* stands for the root.
|
|
138
|
+
*
|
|
139
|
+
* `undefined` when the file cannot be saved: a stored reference the host's
|
|
140
|
+
* resolver does not answer and no URL the gate admits beside it. The button is
|
|
141
|
+
* not drawn then, so a reader is never offered a download that cannot happen.
|
|
142
|
+
*/
|
|
143
|
+
declare function planFileSave(file: StuffFile, options: SavePlanOptions): SaveFile | undefined;
|
|
144
|
+
|
|
145
|
+
export { type SaveFailure, type SaveFile, type SaveFileKind, type SaveFiles, type SavePlan, type SavePlanOptions, type SaveResult, type UnavailableFile, planFileSave, planStuffSave };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"save-plan.js"}
|