@quillmark/quiver 0.38.1 → 0.39.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MIGRATION.md +16 -0
- package/README.md +11 -11
- package/dist/build.d.ts +3 -5
- package/dist/build.js +12 -32
- package/dist/built-loader.d.ts +1 -1
- package/dist/built-loader.js +15 -45
- package/dist/bundle.d.ts +6 -10
- package/dist/bundle.js +28 -37
- package/dist/format.d.ts +5 -5
- package/dist/format.js +5 -5
- package/dist/node.d.ts +1 -1
- package/dist/node.js +3 -3
- package/dist/quiver.d.ts +9 -6
- package/dist/quiver.js +15 -13
- package/dist/ref.d.ts +1 -1
- package/dist/ref.js +1 -1
- package/dist/signature.d.ts +1 -1
- package/dist/signature.js +4 -3
- package/dist/source-loader.d.ts +1 -1
- package/dist/source-loader.js +2 -2
- package/package.json +2 -2
package/MIGRATION.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
Upgrade notes per range. The [CHANGELOG](CHANGELOG.md) is the full record; this page is the subset that breaks a working consumer, ordered by how late you find out.
|
|
4
4
|
|
|
5
|
+
## 0.38 → 0.39
|
|
6
|
+
|
|
7
|
+
### Rebuild every artifact
|
|
8
|
+
|
|
9
|
+
A built quiver is `quiver.json` beside one `<name>@<x.y.z>.<digest>.quill` bundle per quill, each carrying its quill's fonts; `fonts/` is gone, and `quiver.json` and `latest.json` state format 3. A format-2 artifact under 0.39 is refused as `quiver_invalid` naming the rebuild; a 0.39 artifact under an older reader is refused with the upgrade named. Re-run `build`; nothing in the source layout changes.
|
|
10
|
+
|
|
11
|
+
`quillkit` packs and reads with the `@quillmark/quiver` it carries, so a quillkit deploy moves to format 3 with the quillkit release that carries 0.39, and nothing in the collection pins this package.
|
|
12
|
+
|
|
13
|
+
A host rule naming `fonts/*` or `*.zip` under a built quiver names `*.quill` now. A server whose static handler types files by extension serves `.quill` as `application/vnd.quillmark.quill+zip` or `application/octet-stream`; the loader reads the bytes whatever the type.
|
|
14
|
+
|
|
15
|
+
### Runtime, not build time
|
|
16
|
+
|
|
17
|
+
**A quill whose tree holds a file name the `.quill` format refuses fails `build`**: one that holds `\`, `:` or NUL, which a filesystem on Linux or macOS allows. Rename the file.
|
|
18
|
+
|
|
19
|
+
**A bundle the `.quill` format refuses fails `getQuill` with the `QuillmarkError` `Quill.fromBundle` throws**, its diagnostic a `quill::bundle_*` code, where an over-budget or misnamed entry was a `QuiverError` coded `quiver_invalid`. Code that sorts a `getQuill` failure by `QuiverError.code` reads `diagnostics` off the error `isQuillmarkError` narrows. The `@quillmark/wasm` peer floor is `>=0.133.0-0`.
|
|
20
|
+
|
|
5
21
|
## 0.29 → 0.30
|
|
6
22
|
|
|
7
23
|
### Rebuild every artifact, and move quiver and quillkit together
|
package/README.md
CHANGED
|
@@ -37,17 +37,17 @@ const root = dirname(createRequire(import.meta.url).resolve('@org/my-quiver/Quiv
|
|
|
37
37
|
const quiver = await fromDir(root);
|
|
38
38
|
const engine = new Engine();
|
|
39
39
|
|
|
40
|
-
const { Document } = await init(); //
|
|
40
|
+
const { Document } = await init(); // Document is exported only through init()
|
|
41
41
|
const doc = Document.fromMarkdown(markdownString);
|
|
42
42
|
const quill = await quiver.getQuill(doc.quillRef);
|
|
43
43
|
const result = await engine.render(quill, doc, { format: 'pdf' });
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
-
`getQuill(ref)` is the only way to obtain a quill from a quiver, and the only entry point most consumers need. It accepts selector refs (`"memo"`, `"memo@1.0"`) and canonical ones (`"memo@1.0.0"`), resolves the selector, materializes the quill via `Quill.fromTree
|
|
46
|
+
`getQuill(ref)` is the only way to obtain a quill from a quiver, and the only entry point most consumers need. It accepts selector refs (`"memo"`, `"memo@1.0"`) and canonical ones (`"memo@1.0.0"`), resolves the selector, materializes the quill via `Quill.fromBundle` (a built quiver) or `Quill.fromTree` (a source one), and caches one instance per canonical ref for the quiver's lifetime; concurrent calls for the same ref coalesce into a single load. It awaits `init()` itself, so instantiating the core is not a precondition of calling it.
|
|
47
47
|
|
|
48
48
|
That quill is **borrowed, not owned**: every caller asking for that ref gets the same instance, so `free()` on it hands the next caller a freed handle. Code that owns its quill mints one from `(await quiver.getQuill(ref)).toTree()` and frees that.
|
|
49
49
|
|
|
50
|
-
This package produces quills; `@quillmark/wasm` renders them. A quill from `getQuill` is engine-free portable data — schema inspection, validation,
|
|
50
|
+
This package produces quills; `@quillmark/wasm` renders them. A quill from `getQuill` is engine-free portable data — schema inspection, validation, `authoringSchema`, `seedDocument()` — and passes straight to `engine.render(quill, doc)`, which routes on `quill.backendId`, loads that backend, clones both handles into its memory, renders, and frees the clones. There is no boundary-crossing step to perform. `Engine.render`, `open` and `supportedFormats` are **async**. The canonical `Quill` / `Document` / `Engine` types are not re-exported here; import them from the `@quillmark/wasm` peer, their single source of truth.
|
|
51
51
|
|
|
52
52
|
One narrower verb sits beside `getQuill`: `resolve(ref)` returns the canonical ref without materializing anything, and is **sync** (the catalog is in memory from the moment the quiver is built).
|
|
53
53
|
|
|
@@ -82,7 +82,7 @@ import { Quiver } from '@quillmark/quiver';
|
|
|
82
82
|
const quiver = await Quiver.fromBuiltUrl('/quivers/my-quiver/');
|
|
83
83
|
const engine = new Engine();
|
|
84
84
|
|
|
85
|
-
const { Document } = await init(); //
|
|
85
|
+
const { Document } = await init(); // Document is exported only through init()
|
|
86
86
|
const doc = Document.fromMarkdown(markdownString);
|
|
87
87
|
const quill = await quiver.getQuill(doc.quillRef);
|
|
88
88
|
const result = await engine.render(quill, doc, { format: 'pdf' });
|
|
@@ -103,7 +103,7 @@ const quiver = await fromBuiltDir('./static/quills/my-quiver');
|
|
|
103
103
|
|
|
104
104
|
## Server-side runtime (no filesystem)
|
|
105
105
|
|
|
106
|
-
A serverless function's packed artifact is not on a path the invocation can read, so `fromBuiltDir` cannot see it. Hand over the bytes instead, keyed by artifact-relative path as `build` writes them (`quiver.json`, `<name>@<x.y.z>.<digest>.
|
|
106
|
+
A serverless function's packed artifact is not on a path the invocation can read, so `fromBuiltDir` cannot see it. Hand over the bytes instead, keyed by artifact-relative path as `build` writes them (`quiver.json`, `<name>@<x.y.z>.<digest>.quill`):
|
|
107
107
|
|
|
108
108
|
```ts
|
|
109
109
|
import { Quiver } from '@quillmark/quiver';
|
|
@@ -115,9 +115,9 @@ Nothing is fetched, so nothing self-fetches over your own load balancer. The map
|
|
|
115
115
|
|
|
116
116
|
## The `quiver.json` index
|
|
117
117
|
|
|
118
|
-
`Quiver.fromBuiltUrl(url)` first fetches `<url>/quiver.json`: the format and the catalog, naming each quill's bundle
|
|
118
|
+
`Quiver.fromBuiltUrl(url)` first fetches `<url>/quiver.json`: the format and the catalog, naming each quill's bundle. Every other name carries the digest of its own bytes, so a changed file is a changed name and is safe to cache forever; `quiver.json` is not, and a cache layer serving a stale one silently pins the client to the old catalog. It is therefore the one request fetched `no-cache` (revalidate with the origin; a 304 still serves from disk), and every other request is `force-cache`, read once more with `reload` if what the cache answered fails. Both are the browser layer only: a stale CDN edge is answered by that host's cache headers, and `quillkit`'s README states the whole contract for a deploy serving a client over one.
|
|
119
119
|
|
|
120
|
-
A `getQuill` reads one bundle
|
|
120
|
+
A `getQuill` reads one bundle, nothing of another quill. Each bundle is a `.quill` bundle: the quill's whole tree as one zip, fonts included, `Quill.yaml` at the root — the file `Quill.fromBundle` in `@quillmark/wasm`, `Quill.from_bundle` in Python and the `quillmark` CLI read. A release that edits a template re-downloads that quill's bundle, fonts and all. A tab still holding the previous release's `quiver.json` rereads it once when a read fails.
|
|
121
121
|
|
|
122
122
|
## What a quiver is trusted to be
|
|
123
123
|
|
|
@@ -127,7 +127,7 @@ Nothing checks fetched bytes against their names: the digests are there for cach
|
|
|
127
127
|
|
|
128
128
|
## Error handling
|
|
129
129
|
|
|
130
|
-
Every error is a `QuiverError` carrying a `code` from a closed set — `invalid_ref`, `quill_not_found`, `quiver_invalid`, `transport_error` — a human-readable `message`, and the offending `ref` where there is one.
|
|
130
|
+
Every error is a `QuiverError` carrying a `code` from a closed set — `invalid_ref`, `quill_not_found`, `quiver_invalid`, `transport_error` — a human-readable `message`, and the offending `ref` where there is one. A quill that does not load, a bundle the `.quill` format refuses among them, is the exception: `getQuill` throws what `Quill.fromBundle` or `Quill.fromTree` threw, a `QuillmarkError` carrying its `diagnostics`.
|
|
131
131
|
|
|
132
132
|
```ts
|
|
133
133
|
import { QuiverError } from '@quillmark/quiver';
|
|
@@ -169,15 +169,15 @@ A consumer reaches an installed quiver by resolving `<specifier>/Quiver.yaml`, w
|
|
|
169
169
|
|
|
170
170
|
## Authoring is [quillkit](../quillkit#readme)'s
|
|
171
171
|
|
|
172
|
-
This package is a library: it loads a quiver and packs one, and it has no CLI. The verbs a quill author runs (
|
|
172
|
+
This package is a library: it loads a quiver and packs one, and it has no CLI. The verbs a quill author runs (start, gate, look at, build) are `quillkit`'s, which carries this package at an exact version, so a quiver installs the tool alone and the format it is packed in is the one quillkit's clients read:
|
|
173
173
|
|
|
174
174
|
```sh
|
|
175
|
-
npm install --save-dev
|
|
175
|
+
npm install --save-dev quillkit
|
|
176
176
|
```
|
|
177
177
|
|
|
178
178
|
```jsonc
|
|
179
179
|
// package.json
|
|
180
180
|
{
|
|
181
|
-
"scripts": { "test": "quillkit
|
|
181
|
+
"scripts": { "test": "quillkit check" }
|
|
182
182
|
}
|
|
183
183
|
```
|
package/dist/build.d.ts
CHANGED
|
@@ -20,11 +20,9 @@ export interface BuildOptions {
|
|
|
20
20
|
* Output layout. Every name but the two JSON documents carries the SHA-256 of what it
|
|
21
21
|
* names, which is what makes it safe to cache forever:
|
|
22
22
|
* outDir/
|
|
23
|
-
* quiver.json
|
|
24
|
-
* latest.json
|
|
25
|
-
* <name>@<version>.<sha256:32>.
|
|
26
|
-
* fonts/
|
|
27
|
-
* <sha256> # dehydrated font bytes (full hash, no ext)
|
|
23
|
+
* quiver.json # the format and the catalog
|
|
24
|
+
* latest.json # the format alone, for a reader of format 1
|
|
25
|
+
* <name>@<version>.<sha256:32>.quill # one `.quill` bundle per quill, fonts and all
|
|
28
26
|
*
|
|
29
27
|
* A generation is assembled beside outDir and moved in whole, so a reader
|
|
30
28
|
* fetching mid-build reads the previous one rather than a torn tree, and a
|
package/dist/build.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* `node:crypto` into a browser bundle.
|
|
7
7
|
*/
|
|
8
8
|
import { QuiverError } from './errors.js';
|
|
9
|
-
import { packFiles } from './bundle.js';
|
|
9
|
+
import { BUNDLE_EXTENSION, packFiles } from './bundle.js';
|
|
10
10
|
import { FORMAT, INDEX, LEGACY_POINTER } from './format.js';
|
|
11
11
|
import { isFont } from './signature.js';
|
|
12
12
|
import { isDraft } from './semver.js';
|
|
@@ -24,7 +24,7 @@ const stagesOf = (out) => ({
|
|
|
24
24
|
/**
|
|
25
25
|
* The build deletes each of the three trees it owns, so an outDir that is, or contains,
|
|
26
26
|
* the source quiver or the working directory deletes the thing the caller was building
|
|
27
|
-
* from. `quillkit build --out .` and a mistyped `--out ..` are both one keystroke away,
|
|
27
|
+
* from. `quillkit build quiver --out .` and a mistyped `--out ..` are both one keystroke away,
|
|
28
28
|
* and the failure is unrecoverable, so an outDir that owns the caller is refused up
|
|
29
29
|
* front. The staging siblings are checked too: they are named off outDir and cleared
|
|
30
30
|
* with it, so a caller who avoids one spelling has not avoided the others.
|
|
@@ -57,11 +57,9 @@ function assertSafeOutDir(path, sourceDir, outDir) {
|
|
|
57
57
|
* Output layout. Every name but the two JSON documents carries the SHA-256 of what it
|
|
58
58
|
* names, which is what makes it safe to cache forever:
|
|
59
59
|
* outDir/
|
|
60
|
-
* quiver.json
|
|
61
|
-
* latest.json
|
|
62
|
-
* <name>@<version>.<sha256:32>.
|
|
63
|
-
* fonts/
|
|
64
|
-
* <sha256> # dehydrated font bytes (full hash, no ext)
|
|
60
|
+
* quiver.json # the format and the catalog
|
|
61
|
+
* latest.json # the format alone, for a reader of format 1
|
|
62
|
+
* <name>@<version>.<sha256:32>.quill # one `.quill` bundle per quill, fonts and all
|
|
65
63
|
*
|
|
66
64
|
* A generation is assembled beside outDir and moved in whole, so a reader
|
|
67
65
|
* fetching mid-build reads the previous one rather than a torn tree, and a
|
|
@@ -113,44 +111,26 @@ export async function buildQuiver(sourceDir, outDir, options = {}) {
|
|
|
113
111
|
async function packGeneration() {
|
|
114
112
|
try {
|
|
115
113
|
await rm(stage, { recursive: true, force: true });
|
|
116
|
-
await mkdir(
|
|
114
|
+
await mkdir(stage, { recursive: true });
|
|
117
115
|
}
|
|
118
116
|
catch (err) {
|
|
119
117
|
throw new QuiverError('transport_error', `Failed to prepare output directory "${outDir}": ${err.message}`, { cause: err });
|
|
120
118
|
}
|
|
121
119
|
const quills = [];
|
|
122
|
-
const stored = new Set();
|
|
123
120
|
for (const [quillName, versions] of catalog) {
|
|
124
121
|
for (const version of versions) {
|
|
125
122
|
if (!options.drafts && isDraft(version))
|
|
126
123
|
continue;
|
|
127
124
|
const quillDir = join(sourceDir, 'quills', quillName, version);
|
|
128
125
|
const tree = await readQuillTree(quillDir);
|
|
129
|
-
const fonts = {};
|
|
130
126
|
const contentRecord = {};
|
|
131
127
|
for (const [rel, bytes] of tree) {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
136
|
-
// The loader refuses what does not open as a font, so the build refuses it
|
|
137
|
-
// first, where the author it names can do something about it.
|
|
138
|
-
if (!isFont(bytes, rel)) {
|
|
128
|
+
// Typst substitutes a font it cannot parse without a word, so a font file
|
|
129
|
+
// that is not one is refused where the author it names can fix it.
|
|
130
|
+
if (FONT_EXT.test(rel) && !isFont(bytes, rel)) {
|
|
139
131
|
throw new QuiverError('quiver_invalid', `Quill "${quillName}@${version}": "${rel}" does not open as the font its extension names`, { quiverName: meta.name, version });
|
|
140
132
|
}
|
|
141
|
-
|
|
142
|
-
// prefix would merge into one file.
|
|
143
|
-
const hash = createHash('sha256').update(bytes).digest('hex');
|
|
144
|
-
fonts[rel] = hash;
|
|
145
|
-
if (stored.has(hash))
|
|
146
|
-
continue;
|
|
147
|
-
try {
|
|
148
|
-
await writeFile(join(stage, 'fonts', hash), bytes);
|
|
149
|
-
}
|
|
150
|
-
catch (err) {
|
|
151
|
-
throw new QuiverError('transport_error', `Failed to write font "${join(outDir, 'fonts', hash)}": ${err.message}`, { cause: err });
|
|
152
|
-
}
|
|
153
|
-
stored.add(hash);
|
|
133
|
+
contentRecord[rel] = bytes;
|
|
154
134
|
}
|
|
155
135
|
// The budget `bundle.ts` carries is spent here as well as at the read, so
|
|
156
136
|
// a quill no loader would take is refused by the build that packs it,
|
|
@@ -166,14 +146,14 @@ export async function buildQuiver(sourceDir, outDir, options = {}) {
|
|
|
166
146
|
.update(zipBytes)
|
|
167
147
|
.digest('hex')
|
|
168
148
|
.slice(0, NAME_DIGEST_LENGTH);
|
|
169
|
-
const bundleName = `${quillName}@${version}.${bundleHash}
|
|
149
|
+
const bundleName = `${quillName}@${version}.${bundleHash}.${BUNDLE_EXTENSION}`;
|
|
170
150
|
try {
|
|
171
151
|
await writeFile(join(stage, bundleName), zipBytes);
|
|
172
152
|
}
|
|
173
153
|
catch (err) {
|
|
174
154
|
throw new QuiverError('transport_error', `Failed to write bundle "${join(outDir, bundleName)}": ${err.message}`, { cause: err });
|
|
175
155
|
}
|
|
176
|
-
quills.push({ name: quillName, version, bundle: bundleName
|
|
156
|
+
quills.push({ name: quillName, version, bundle: bundleName });
|
|
177
157
|
}
|
|
178
158
|
}
|
|
179
159
|
// The format is stamped here and read first, so a client older than the tree says so
|
package/dist/built-loader.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Built-quiver loader: reads `quiver.json` through a reader, validates it, and hands back
|
|
3
|
-
* a Quiver whose loader reads
|
|
3
|
+
* a Quiver whose loader reads one `.quill` bundle per quill on demand.
|
|
4
4
|
*
|
|
5
5
|
* Package-internal, and browser-safe: no static `node:` imports at any level.
|
|
6
6
|
*/
|
package/dist/built-loader.js
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Built-quiver loader: reads `quiver.json` through a reader, validates it, and hands back
|
|
3
|
-
* a Quiver whose loader reads
|
|
3
|
+
* a Quiver whose loader reads one `.quill` bundle per quill on demand.
|
|
4
4
|
*
|
|
5
5
|
* Package-internal, and browser-safe: no static `node:` imports at any level.
|
|
6
6
|
*/
|
|
7
7
|
import { QuiverError } from './errors.js';
|
|
8
|
-
import { unpackFiles } from './bundle.js';
|
|
9
8
|
import { isQuillName } from './ref.js';
|
|
10
9
|
import { isCanonicalSemver, compareSemver } from './semver.js';
|
|
11
10
|
import { FORMAT, INDEX } from './format.js';
|
|
12
|
-
import {
|
|
11
|
+
import { isZip } from './signature.js';
|
|
13
12
|
import { createQuiver } from './quiver.js';
|
|
14
13
|
export function httpReader(base) {
|
|
15
14
|
const root = base.endsWith('/') ? base : `${base}/`;
|
|
@@ -44,10 +43,9 @@ export function filesReader(files) {
|
|
|
44
43
|
return bytes;
|
|
45
44
|
};
|
|
46
45
|
}
|
|
47
|
-
// The
|
|
46
|
+
// The shape `build` writes. It admits no separator, so a validated name read off
|
|
48
47
|
// `quiver.json` stays inside the artifact on every reader.
|
|
49
|
-
const BUNDLE_RE = /^[A-Za-z0-9_-]+@[0-9]+\.[0-9]+\.[0-9]+\.[0-9a-f]+\.
|
|
50
|
-
const FONT_HASH_RE = /^[0-9a-f]{64}$/;
|
|
48
|
+
const BUNDLE_RE = /^[A-Za-z0-9_-]+@[0-9]+\.[0-9]+\.[0-9]+\.[0-9a-f]+\.quill$/;
|
|
51
49
|
/** Bytes a host answered with that are not the file named: a `transport_error`, so the
|
|
52
50
|
* reread below gets its turn. */
|
|
53
51
|
function refused(path, what) {
|
|
@@ -72,7 +70,6 @@ async function readChecked(read, path, opens, what) {
|
|
|
72
70
|
class BuiltLoader {
|
|
73
71
|
read;
|
|
74
72
|
entries;
|
|
75
|
-
#fonts = new Map();
|
|
76
73
|
#rereading;
|
|
77
74
|
constructor(read, entries) {
|
|
78
75
|
this.read = read;
|
|
@@ -84,11 +81,11 @@ class BuiltLoader {
|
|
|
84
81
|
* names other files. A reread refusing the index is the error that explains the
|
|
85
82
|
* failure, and is the one thrown.
|
|
86
83
|
*/
|
|
87
|
-
async
|
|
84
|
+
async load(name, version) {
|
|
88
85
|
const key = `${name}@${version}`;
|
|
89
86
|
const entry = this.entries.get(key);
|
|
90
87
|
try {
|
|
91
|
-
return await this.#
|
|
88
|
+
return await this.#bundleOf(entry);
|
|
92
89
|
}
|
|
93
90
|
catch (err) {
|
|
94
91
|
let index;
|
|
@@ -99,9 +96,9 @@ class BuiltLoader {
|
|
|
99
96
|
throw reread instanceof QuiverError && reread.code === 'quiver_invalid' ? reread : err;
|
|
100
97
|
}
|
|
101
98
|
const fresh = index.entries.get(key);
|
|
102
|
-
if (fresh === undefined ||
|
|
99
|
+
if (fresh === undefined || fresh.bundle === entry.bundle)
|
|
103
100
|
throw err;
|
|
104
|
-
return this.#
|
|
101
|
+
return this.#bundleOf(fresh);
|
|
105
102
|
}
|
|
106
103
|
}
|
|
107
104
|
/** One reread in flight, taking every entry both generations carry. */
|
|
@@ -118,30 +115,10 @@ class BuiltLoader {
|
|
|
118
115
|
this.#rereading = undefined;
|
|
119
116
|
}));
|
|
120
117
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const files = unpackFiles(zip);
|
|
124
|
-
await Promise.all(Object.entries(entry.fonts).map(async ([path, hash]) => {
|
|
125
|
-
files[path] = await this.#font(hash, path);
|
|
126
|
-
}));
|
|
127
|
-
return new Map(Object.entries(files));
|
|
128
|
-
}
|
|
129
|
-
/** One read per hash, coalesced; a failure is evicted so a retry reads again. */
|
|
130
|
-
#font(hash, rel) {
|
|
131
|
-
let bytes = this.#fonts.get(hash);
|
|
132
|
-
if (bytes === undefined) {
|
|
133
|
-
bytes = readChecked(this.read, `fonts/${hash}`, (font) => isFont(font, rel), 'a font').catch((err) => {
|
|
134
|
-
this.#fonts.delete(hash);
|
|
135
|
-
throw err;
|
|
136
|
-
});
|
|
137
|
-
this.#fonts.set(hash, bytes);
|
|
138
|
-
}
|
|
139
|
-
return bytes;
|
|
118
|
+
#bundleOf(entry) {
|
|
119
|
+
return readChecked(this.read, entry.bundle, isZip, 'a zip');
|
|
140
120
|
}
|
|
141
121
|
}
|
|
142
|
-
function sameFiles(a, b) {
|
|
143
|
-
return a.bundle === b.bundle && JSON.stringify(a.fonts) === JSON.stringify(b.fonts);
|
|
144
|
-
}
|
|
145
122
|
async function readIndex(read) {
|
|
146
123
|
let bytes;
|
|
147
124
|
try {
|
|
@@ -176,6 +153,10 @@ function parseIndex(raw) {
|
|
|
176
153
|
throw new QuiverError('quiver_invalid', `This quiver is built in format ${format} and this loader reads ${FORMAT}. ` +
|
|
177
154
|
`Upgrade @quillmark/quiver, or for a served client the client itself, which carries the copy that reads this.`);
|
|
178
155
|
}
|
|
156
|
+
if (format < FORMAT) {
|
|
157
|
+
throw new QuiverError('quiver_invalid', `This quiver is built in format ${format} and this loader reads ${FORMAT}. ` +
|
|
158
|
+
`Rebuild it with a @quillmark/quiver that writes format ${FORMAT}, in the collection where it is packed.`);
|
|
159
|
+
}
|
|
179
160
|
const name = obj['name'];
|
|
180
161
|
if (typeof name !== 'string' || name.length === 0) {
|
|
181
162
|
throw new QuiverError('quiver_invalid', `${INDEX} must have a non-empty string "name" field`);
|
|
@@ -214,22 +195,11 @@ function parseIndex(raw) {
|
|
|
214
195
|
if (typeof bundle !== 'string' || !BUNDLE_RE.test(bundle)) {
|
|
215
196
|
throw new QuiverError('quiver_invalid', `${at}.bundle is invalid: "${String(bundle)}"`);
|
|
216
197
|
}
|
|
217
|
-
const declared = e['fonts'];
|
|
218
|
-
if (typeof declared !== 'object' || declared === null || Array.isArray(declared)) {
|
|
219
|
-
throw new QuiverError('quiver_invalid', `${at}.fonts must be an object`);
|
|
220
|
-
}
|
|
221
|
-
const fonts = {};
|
|
222
|
-
for (const [path, hash] of Object.entries(declared)) {
|
|
223
|
-
if (typeof hash !== 'string' || !FONT_HASH_RE.test(hash)) {
|
|
224
|
-
throw new QuiverError('quiver_invalid', `${at}.fonts["${path}"] is not a font hash: ${JSON.stringify(hash)}`);
|
|
225
|
-
}
|
|
226
|
-
fonts[path] = hash;
|
|
227
|
-
}
|
|
228
198
|
const key = `${quillName}@${version}`;
|
|
229
199
|
if (entries.has(key)) {
|
|
230
200
|
throw new QuiverError('quiver_invalid', `Duplicate quill entry in ${INDEX}: "${key}"`);
|
|
231
201
|
}
|
|
232
|
-
entries.set(key, { name: quillName, version, bundle
|
|
202
|
+
entries.set(key, { name: quillName, version, bundle });
|
|
233
203
|
}
|
|
234
204
|
return { name, description, entries };
|
|
235
205
|
}
|
package/dist/bundle.d.ts
CHANGED
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* The `.quill` bundle, written through fflate. The format is Quillmark's (quill-bundle.md
|
|
3
|
+
* in the quillmark repository): one quill's tree as one zip, `Quill.yaml` at the root.
|
|
4
|
+
* `Quill.fromBundle` reads it.
|
|
3
5
|
*/
|
|
6
|
+
/** The extension a `.quill` bundle's name ends in. */
|
|
7
|
+
export declare const BUNDLE_EXTENSION = "quill";
|
|
4
8
|
/**
|
|
5
9
|
* Pack a flat file map into a deterministic zip.
|
|
6
10
|
* Keys are sorted before zipping so insertion order doesn't affect output.
|
|
7
|
-
* Throws `quiver_invalid` where the budget is spent.
|
|
11
|
+
* Throws `quiver_invalid` where the budget is spent or a name is one the format refuses.
|
|
8
12
|
*/
|
|
9
13
|
export declare function packFiles(files: Record<string, Uint8Array>): Uint8Array;
|
|
10
|
-
/**
|
|
11
|
-
* Unpack a zip into a flat file map.
|
|
12
|
-
* Returns { path: Uint8Array } for every file entry in the archive.
|
|
13
|
-
* Throws `quiver_invalid` where the budget is spent. fflate filters and inflates one
|
|
14
|
-
* entry per pass, so the entries the budget already took are resident when it throws —
|
|
15
|
-
* bounded, being what fit inside it.
|
|
16
|
-
*/
|
|
17
|
-
export declare function unpackFiles(data: Uint8Array): Record<string, Uint8Array>;
|
package/dist/bundle.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* The `.quill` bundle, written through fflate. The format is Quillmark's (quill-bundle.md
|
|
3
|
+
* in the quillmark repository): one quill's tree as one zip, `Quill.yaml` at the root.
|
|
4
|
+
* `Quill.fromBundle` reads it.
|
|
3
5
|
*/
|
|
4
|
-
import { zipSync
|
|
6
|
+
import { zipSync } from 'fflate';
|
|
5
7
|
import { QuiverError } from './errors.js';
|
|
8
|
+
/** The extension a `.quill` bundle's name ends in. */
|
|
9
|
+
export const BUNDLE_EXTENSION = 'quill';
|
|
6
10
|
/**
|
|
7
11
|
* Fixed epoch mtime for deterministic zip output.
|
|
8
12
|
*
|
|
@@ -15,29 +19,31 @@ import { QuiverError } from './errors.js';
|
|
|
15
19
|
*/
|
|
16
20
|
const ZIP_EPOCH = new Date(1980, 0, 1, 0, 0, 0, 0);
|
|
17
21
|
/**
|
|
18
|
-
* What a bundle may hold
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* is a dependency pinned like any other (README, "What a quiver is trusted to be") and
|
|
23
|
-
* what a budget bounds there is a build that packed something enormous. Both ends spend
|
|
24
|
-
* it, so an artifact no loader would read is refused where it is written.
|
|
25
|
-
*
|
|
26
|
-
* Fonts are not in here: `build` writes them under `fonts/` and `quiver.json` names them,
|
|
27
|
-
* leaving a bundle its markdown, its Typst, and what those inline.
|
|
22
|
+
* What a bundle may hold: the `.quill` format's budget, which every Quillmark runtime
|
|
23
|
+
* spends on the same three bounds. A constant rather than something a consumer raises,
|
|
24
|
+
* because the format fixes it. The reader spends it too, so an artifact no loader would
|
|
25
|
+
* read is refused where it is written.
|
|
28
26
|
*/
|
|
29
27
|
const MAX_BUNDLE_BYTES = 64 * 1024 * 1024;
|
|
30
|
-
const MAX_ENTRY_BYTES =
|
|
28
|
+
const MAX_ENTRY_BYTES = 32 * 1024 * 1024;
|
|
31
29
|
const MAX_BUNDLE_ENTRIES = 2048;
|
|
32
30
|
/**
|
|
33
|
-
* The
|
|
34
|
-
*
|
|
35
|
-
* Unpacking spends the sizes the central directory declares, and those are the sizes
|
|
36
|
-
* fflate allocates: each entry inflates into a buffer of exactly its declared size,
|
|
37
|
-
* which never grows. So a header cannot buy more than it declares — it can only
|
|
38
|
-
* understate itself and truncate its own entry, which is a corrupt bundle rather than an
|
|
39
|
-
* allocation.
|
|
31
|
+
* The `.quill` format's entry names: relative, `/`-separated, and no segment empty, `.`
|
|
32
|
+
* or `..`, nor holding `\`, `:` or NUL. Throws `quiver_invalid`.
|
|
40
33
|
*/
|
|
34
|
+
function checkPath(name) {
|
|
35
|
+
const why = name.startsWith('/')
|
|
36
|
+
? 'is absolute'
|
|
37
|
+
: /[\\:\0]/.test(name)
|
|
38
|
+
? 'holds a \\, : or NUL'
|
|
39
|
+
: name.split('/').some((s) => s === '' || s === '.' || s === '..')
|
|
40
|
+
? 'has an empty, "." or ".." segment'
|
|
41
|
+
: undefined;
|
|
42
|
+
if (why !== undefined) {
|
|
43
|
+
throw new QuiverError('quiver_invalid', `Bundled file "${name}" ${why}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/** The budget, spent one entry at a time. Throws `quiver_invalid`. */
|
|
41
47
|
function bundleBudget() {
|
|
42
48
|
let total = 0;
|
|
43
49
|
let count = 0;
|
|
@@ -57,7 +63,7 @@ function bundleBudget() {
|
|
|
57
63
|
/**
|
|
58
64
|
* Pack a flat file map into a deterministic zip.
|
|
59
65
|
* Keys are sorted before zipping so insertion order doesn't affect output.
|
|
60
|
-
* Throws `quiver_invalid` where the budget is spent.
|
|
66
|
+
* Throws `quiver_invalid` where the budget is spent or a name is one the format refuses.
|
|
61
67
|
*/
|
|
62
68
|
export function packFiles(files) {
|
|
63
69
|
const spend = bundleBudget();
|
|
@@ -65,24 +71,9 @@ export function packFiles(files) {
|
|
|
65
71
|
const input = {};
|
|
66
72
|
for (const key of sorted) {
|
|
67
73
|
const bytes = files[key];
|
|
74
|
+
checkPath(key);
|
|
68
75
|
spend(key, bytes.length);
|
|
69
76
|
input[key] = [bytes, { mtime: ZIP_EPOCH }];
|
|
70
77
|
}
|
|
71
78
|
return zipSync(input, { level: 6 });
|
|
72
79
|
}
|
|
73
|
-
/**
|
|
74
|
-
* Unpack a zip into a flat file map.
|
|
75
|
-
* Returns { path: Uint8Array } for every file entry in the archive.
|
|
76
|
-
* Throws `quiver_invalid` where the budget is spent. fflate filters and inflates one
|
|
77
|
-
* entry per pass, so the entries the budget already took are resident when it throws —
|
|
78
|
-
* bounded, being what fit inside it.
|
|
79
|
-
*/
|
|
80
|
-
export function unpackFiles(data) {
|
|
81
|
-
const spend = bundleBudget();
|
|
82
|
-
return unzipSync(data, {
|
|
83
|
-
filter: ({ name, originalSize }) => {
|
|
84
|
-
spend(name, originalSize);
|
|
85
|
-
return true;
|
|
86
|
-
}
|
|
87
|
-
});
|
|
88
|
-
}
|
package/dist/format.d.ts
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
* other name is read off. */
|
|
3
3
|
export declare const INDEX = "quiver.json";
|
|
4
4
|
/**
|
|
5
|
-
* The artifact's format, stamped into `quiver.json` and
|
|
6
|
-
* §"The index"). A change a reader assuming the previous shape would misread moves it
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* The artifact's format, stamped into `quiver.json` and read at exactly this value (QUIVER
|
|
6
|
+
* §"The index"). A change a reader assuming the previous shape would misread moves it; an
|
|
7
|
+
* added field a reader can skip does not. Format 3 is one `.quill` bundle per quill and no
|
|
8
|
+
* `fonts/`.
|
|
9
9
|
*/
|
|
10
|
-
export declare const FORMAT =
|
|
10
|
+
export declare const FORMAT = 3;
|
|
11
11
|
/**
|
|
12
12
|
* What a reader of format 1 fetches first. It reads `format` off it before anything else,
|
|
13
13
|
* so a file carrying this format and nothing more is refused there with the upgrade named,
|
package/dist/format.js
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
* other name is read off. */
|
|
3
3
|
export const INDEX = 'quiver.json';
|
|
4
4
|
/**
|
|
5
|
-
* The artifact's format, stamped into `quiver.json` and
|
|
6
|
-
* §"The index"). A change a reader assuming the previous shape would misread moves it
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* The artifact's format, stamped into `quiver.json` and read at exactly this value (QUIVER
|
|
6
|
+
* §"The index"). A change a reader assuming the previous shape would misread moves it; an
|
|
7
|
+
* added field a reader can skip does not. Format 3 is one `.quill` bundle per quill and no
|
|
8
|
+
* `fonts/`.
|
|
9
9
|
*/
|
|
10
|
-
export const FORMAT =
|
|
10
|
+
export const FORMAT = 3;
|
|
11
11
|
/**
|
|
12
12
|
* What a reader of format 1 fetches first. It reads `format` off it before anything else,
|
|
13
13
|
* so a file carrying this format and nothing more is refused there with the upgrade named,
|
package/dist/node.d.ts
CHANGED
|
@@ -28,7 +28,7 @@ import type { BuildOptions } from './build.js';
|
|
|
28
28
|
export declare function fromDir(pathOrFileUrl: string): Promise<Quiver>;
|
|
29
29
|
/**
|
|
30
30
|
* Loads a packed (build-output) quiver from a local directory containing
|
|
31
|
-
* `quiver.json` and the bundles
|
|
31
|
+
* `quiver.json` and the `.quill` bundles written by `build`.
|
|
32
32
|
* Symmetric to `Quiver.fromBuiltUrl(url)` but reads from disk instead of HTTP:
|
|
33
33
|
* no network required.
|
|
34
34
|
*
|
package/dist/node.js
CHANGED
|
@@ -40,7 +40,7 @@ export async function fromDir(pathOrFileUrl) {
|
|
|
40
40
|
}
|
|
41
41
|
/**
|
|
42
42
|
* Loads a packed (build-output) quiver from a local directory containing
|
|
43
|
-
* `quiver.json` and the bundles
|
|
43
|
+
* `quiver.json` and the `.quill` bundles written by `build`.
|
|
44
44
|
* Symmetric to `Quiver.fromBuiltUrl(url)` but reads from disk instead of HTTP:
|
|
45
45
|
* no network required.
|
|
46
46
|
*
|
|
@@ -79,6 +79,6 @@ export async function build(sourceDir, outDir, options) {
|
|
|
79
79
|
export { Quiver } from './quiver.js';
|
|
80
80
|
export { QuiverError } from './errors.js';
|
|
81
81
|
export { isDraft } from './semver.js';
|
|
82
|
-
//
|
|
82
|
+
// The canonical types (`Quill`, `Document`, `Engine`, `RenderResult`, …) are not
|
|
83
83
|
// re-exported: import them straight from the `@quillmark/wasm` peer dependency,
|
|
84
|
-
// which is
|
|
84
|
+
// which is their single source of truth.
|
package/dist/quiver.d.ts
CHANGED
|
@@ -8,9 +8,11 @@
|
|
|
8
8
|
* the same either way.
|
|
9
9
|
*/
|
|
10
10
|
import type { Quill } from '@quillmark/wasm';
|
|
11
|
+
/** A quill as a loader holds it: a source quiver's file tree, or a built one's `.quill` bundle. */
|
|
12
|
+
export type QuillSource = Map<string, Uint8Array> | Uint8Array;
|
|
11
13
|
/** Loader strategy: source or build output. Package-internal. */
|
|
12
14
|
export interface QuiverLoader {
|
|
13
|
-
|
|
15
|
+
load(name: string, version: string): Promise<QuillSource>;
|
|
14
16
|
}
|
|
15
17
|
/**
|
|
16
18
|
* The private constructor's only door, closed over by the static block below.
|
|
@@ -42,8 +44,8 @@ export declare class Quiver {
|
|
|
42
44
|
static fromBuiltUrl(url: string): Promise<Quiver>;
|
|
43
45
|
/**
|
|
44
46
|
* Browser-safe factory. Loads build output from the bytes themselves, keyed
|
|
45
|
-
* by artifact-relative path (`quiver.json`, `<name>@<x.y.z>.<digest>.
|
|
46
|
-
*
|
|
47
|
+
* by artifact-relative path (`quiver.json`, `<name>@<x.y.z>.<digest>.quill`)
|
|
48
|
+
* — the shape `build` writes and `fromBuiltDir` reads back.
|
|
47
49
|
*
|
|
48
50
|
* Nothing is fetched, so a runtime whose artifact is not on a path it can
|
|
49
51
|
* read (a serverless function, a bundler that inlines it, a test) reaches
|
|
@@ -71,7 +73,7 @@ export declare class Quiver {
|
|
|
71
73
|
*
|
|
72
74
|
* Sync, and in-memory by construction rather than by implementation: every
|
|
73
75
|
* loader materializes the catalog as the quiver is built, and `QuiverLoader`
|
|
74
|
-
* carries one verb, `
|
|
76
|
+
* carries one verb, `load`, which resolution never reaches.
|
|
75
77
|
*
|
|
76
78
|
* Throws:
|
|
77
79
|
* - `invalid_ref` if ref fails parseQuillRef
|
|
@@ -83,7 +85,7 @@ export declare class Quiver {
|
|
|
83
85
|
*
|
|
84
86
|
* The returned quill is materialized from the `@quillmark/wasm` root export
|
|
85
87
|
* — it is engine-free, portable data suitable for schema inspection,
|
|
86
|
-
* validation,
|
|
88
|
+
* validation, authoring-schema access, and document seeding.
|
|
87
89
|
*
|
|
88
90
|
* A core `Quill` renders directly: pass it to `engine.render(quill, doc)`.
|
|
89
91
|
*
|
|
@@ -100,7 +102,8 @@ export declare class Quiver {
|
|
|
100
102
|
* - `invalid_ref` if ref is malformed
|
|
101
103
|
* - `quill_not_found` if ref does not match any version in this quiver
|
|
102
104
|
* - propagates I/O errors from the loader unchanged
|
|
103
|
-
* - propagates
|
|
105
|
+
* - propagates errors from Quill.fromTree() or Quill.fromBundle() unchanged,
|
|
106
|
+
* a bundle the format refuses among them as `quill::bundle_*`
|
|
104
107
|
*/
|
|
105
108
|
getQuill(ref: string): Promise<Quill>;
|
|
106
109
|
}
|
package/dist/quiver.js
CHANGED
|
@@ -63,8 +63,8 @@ export class Quiver {
|
|
|
63
63
|
}
|
|
64
64
|
/**
|
|
65
65
|
* Browser-safe factory. Loads build output from the bytes themselves, keyed
|
|
66
|
-
* by artifact-relative path (`quiver.json`, `<name>@<x.y.z>.<digest>.
|
|
67
|
-
*
|
|
66
|
+
* by artifact-relative path (`quiver.json`, `<name>@<x.y.z>.<digest>.quill`)
|
|
67
|
+
* — the shape `build` writes and `fromBuiltDir` reads back.
|
|
68
68
|
*
|
|
69
69
|
* Nothing is fetched, so a runtime whose artifact is not on a path it can
|
|
70
70
|
* read (a serverless function, a bundler that inlines it, a test) reaches
|
|
@@ -99,7 +99,7 @@ export class Quiver {
|
|
|
99
99
|
*
|
|
100
100
|
* Sync, and in-memory by construction rather than by implementation: every
|
|
101
101
|
* loader materializes the catalog as the quiver is built, and `QuiverLoader`
|
|
102
|
-
* carries one verb, `
|
|
102
|
+
* carries one verb, `load`, which resolution never reaches.
|
|
103
103
|
*
|
|
104
104
|
* Throws:
|
|
105
105
|
* - `invalid_ref` if ref fails parseQuillRef
|
|
@@ -124,7 +124,7 @@ export class Quiver {
|
|
|
124
124
|
*
|
|
125
125
|
* The returned quill is materialized from the `@quillmark/wasm` root export
|
|
126
126
|
* — it is engine-free, portable data suitable for schema inspection,
|
|
127
|
-
* validation,
|
|
127
|
+
* validation, authoring-schema access, and document seeding.
|
|
128
128
|
*
|
|
129
129
|
* A core `Quill` renders directly: pass it to `engine.render(quill, doc)`.
|
|
130
130
|
*
|
|
@@ -141,7 +141,8 @@ export class Quiver {
|
|
|
141
141
|
* - `invalid_ref` if ref is malformed
|
|
142
142
|
* - `quill_not_found` if ref does not match any version in this quiver
|
|
143
143
|
* - propagates I/O errors from the loader unchanged
|
|
144
|
-
* - propagates
|
|
144
|
+
* - propagates errors from Quill.fromTree() or Quill.fromBundle() unchanged,
|
|
145
|
+
* a bundle the format refuses among them as `quill::bundle_*`
|
|
145
146
|
*/
|
|
146
147
|
async getQuill(ref) {
|
|
147
148
|
const canonicalRef = this.resolve(ref);
|
|
@@ -156,12 +157,13 @@ export class Quiver {
|
|
|
156
157
|
return entry;
|
|
157
158
|
}
|
|
158
159
|
/**
|
|
159
|
-
* Internal: load the tree + construct via Quill.fromTree
|
|
160
|
+
* Internal: load the tree or bundle + construct via Quill.fromTree or
|
|
161
|
+
* Quill.fromBundle. Errors propagate.
|
|
160
162
|
*
|
|
161
|
-
* The
|
|
162
|
-
* materialized result, and it is the one cache. A second
|
|
163
|
-
* would buy a retry after a
|
|
164
|
-
*
|
|
163
|
+
* The source is held only for the length of this call: the quill cache holds the
|
|
164
|
+
* materialized result, and it is the one cache. A second source cache beside it
|
|
165
|
+
* would buy a retry after a throw its refetch, which is a saved round-trip on the
|
|
166
|
+
* path where the quill is broken.
|
|
165
167
|
*
|
|
166
168
|
* Every caller arrives through `resolve` or the catalog itself, so the ref is
|
|
167
169
|
* catalog-backed by construction and the loader needs no gate.
|
|
@@ -172,11 +174,11 @@ export class Quiver {
|
|
|
172
174
|
*/
|
|
173
175
|
async #materializeQuill(canonicalRef) {
|
|
174
176
|
const at = canonicalRef.indexOf('@');
|
|
175
|
-
const [{ Quill },
|
|
177
|
+
const [{ Quill }, source] = await Promise.all([
|
|
176
178
|
init(),
|
|
177
|
-
this.#loader.
|
|
179
|
+
this.#loader.load(canonicalRef.slice(0, at), canonicalRef.slice(at + 1))
|
|
178
180
|
]);
|
|
179
|
-
return Quill.fromTree(
|
|
181
|
+
return source instanceof Uint8Array ? Quill.fromBundle(source) : Quill.fromTree(source);
|
|
180
182
|
}
|
|
181
183
|
}
|
|
182
184
|
export { create as createQuiver };
|
package/dist/ref.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ export interface ParsedQuillRef {
|
|
|
8
8
|
}
|
|
9
9
|
/**
|
|
10
10
|
* Whether `name` is a quill name: `[a-z][a-z0-9_]*`, at most 64 characters, the
|
|
11
|
-
*
|
|
11
|
+
* Quillmark's rule. A quiver's own name and every catalog row are held to it.
|
|
12
12
|
*/
|
|
13
13
|
export declare function isQuillName(name: string): boolean;
|
|
14
14
|
/**
|
package/dist/ref.js
CHANGED
|
@@ -3,7 +3,7 @@ const NAME_RE = /^[a-z][a-z0-9_]{0,63}$/;
|
|
|
3
3
|
const SELECTOR_RE = /^\d+\.\d+(\.\d+)?$/;
|
|
4
4
|
/**
|
|
5
5
|
* Whether `name` is a quill name: `[a-z][a-z0-9_]*`, at most 64 characters, the
|
|
6
|
-
*
|
|
6
|
+
* Quillmark's rule. A quiver's own name and every catalog row are held to it.
|
|
7
7
|
*/
|
|
8
8
|
export function isQuillName(name) {
|
|
9
9
|
return NAME_RE.test(name);
|
package/dist/signature.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* TrueType, OpenType or a collection of either; WOFF or WOFF2 where `path` names one.
|
|
3
|
-
*
|
|
3
|
+
* Quillmark parses sfnt alone, so a `.ttf` holding WOFF bytes is refused.
|
|
4
4
|
*/
|
|
5
5
|
export declare const isFont: (bytes: Uint8Array, path: string) => boolean;
|
|
6
6
|
export declare const isZip: (bytes: Uint8Array) => boolean;
|
package/dist/signature.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Leading bytes. A host answering a missing name with a page of its own answers 200, so a
|
|
3
|
+
* bundle is read only once it opens as a zip; and Typst substitutes a font it cannot parse
|
|
4
|
+
* without a word, so `build` packs a font file only once it opens as a font.
|
|
4
5
|
*/
|
|
5
6
|
const SFNT = ['\0\x01\0\0', 'OTTO', 'true', 'ttcf'];
|
|
6
7
|
const WOFF = ['wOFF', 'wOF2'];
|
|
7
8
|
const opens = (bytes, magic) => bytes.length >= magic.length && [...magic].every((c, i) => bytes[i] === c.charCodeAt(0));
|
|
8
9
|
/**
|
|
9
10
|
* TrueType, OpenType or a collection of either; WOFF or WOFF2 where `path` names one.
|
|
10
|
-
*
|
|
11
|
+
* Quillmark parses sfnt alone, so a `.ttf` holding WOFF bytes is refused.
|
|
11
12
|
*/
|
|
12
13
|
export const isFont = (bytes, path) => (/\.woff2?$/i.test(path) ? WOFF : SFNT).some((magic) => opens(bytes, magic));
|
|
13
14
|
export const isZip = (bytes) => opens(bytes, 'PK\x03\x04');
|
package/dist/source-loader.d.ts
CHANGED
|
@@ -38,5 +38,5 @@ export declare function readQuillTree(quillDir: string): Promise<Map<string, Uin
|
|
|
38
38
|
export declare class SourceLoader implements QuiverLoader {
|
|
39
39
|
private readonly rootDir;
|
|
40
40
|
constructor(rootDir: string);
|
|
41
|
-
|
|
41
|
+
load(name: string, version: string): Promise<Map<string, Uint8Array>>;
|
|
42
42
|
}
|
package/dist/source-loader.js
CHANGED
|
@@ -34,7 +34,7 @@ function refuseUnnamedQuill(quillName, quiverName) {
|
|
|
34
34
|
* Refuse a `Quill.yaml` whose `quill.name` or `quill.version` disagrees with the
|
|
35
35
|
* path it sits at: the catalog serves the path, and a document saved from the quill
|
|
36
36
|
* names what `Quill.yaml` says. A canonical version has one spelling, so the declared
|
|
37
|
-
* one is compared as written. A field absent or not a string is
|
|
37
|
+
* one is compared as written. A field absent or not a string is Quillmark's to report
|
|
38
38
|
* when the quill loads.
|
|
39
39
|
*/
|
|
40
40
|
function refuseMisplacedQuill(raw, name, version, quiverName) {
|
|
@@ -226,7 +226,7 @@ export class SourceLoader {
|
|
|
226
226
|
constructor(rootDir) {
|
|
227
227
|
this.rootDir = rootDir;
|
|
228
228
|
}
|
|
229
|
-
async
|
|
229
|
+
async load(name, version) {
|
|
230
230
|
const quillDir = join(this.rootDir, 'quills', name, version);
|
|
231
231
|
return readQuillTree(quillDir);
|
|
232
232
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quillmark/quiver",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.39.0",
|
|
4
4
|
"description": "A quiver of Quillmark quills: load one, pack one, hand out quills.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"NOTICE"
|
|
43
43
|
],
|
|
44
44
|
"peerDependencies": {
|
|
45
|
-
"@quillmark/wasm": ">=0.
|
|
45
|
+
"@quillmark/wasm": ">=0.133.0-0"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"fflate": "^0.8.2",
|