@quillmark/quiver 0.2.0 → 0.4.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/PROGRAM.md +202 -123
- package/README.md +88 -35
- package/dist/build.d.ts +25 -0
- package/dist/{pack.js → build.js} +7 -7
- package/dist/{packed-loader.d.ts → built-loader.d.ts} +8 -8
- package/dist/{packed-loader.js → built-loader.js} +10 -10
- package/dist/engine-types.d.ts +1 -1
- package/dist/engine-types.js +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +0 -1
- package/dist/node.js +1 -1
- package/dist/quiver.d.ts +70 -23
- package/dist/quiver.js +180 -36
- package/dist/source-loader.d.ts +1 -1
- package/dist/source-loader.js +1 -1
- package/dist/testing.d.ts +14 -25
- package/dist/testing.js +31 -88
- package/dist/transports/http-transport.d.ts +3 -3
- package/dist/transports/http-transport.js +1 -1
- package/package.json +4 -10
- package/dist/pack.d.ts +0 -25
- package/dist/registry.d.ts +0 -39
- package/dist/registry.js +0 -115
- package/dist/transports/fs-transport.d.ts +0 -14
- package/dist/transports/fs-transport.js +0 -33
package/PROGRAM.md
CHANGED
|
@@ -18,28 +18,43 @@ This means Quiver must own quill selection and lifecycle entirely. There is no e
|
|
|
18
18
|
|
|
19
19
|
## Core Decisions
|
|
20
20
|
|
|
21
|
-
### 1)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
- `
|
|
39
|
-
|
|
40
|
-
-
|
|
41
|
-
|
|
42
|
-
|
|
21
|
+
### 1) One Authored Shape; A Build Output Derived From It
|
|
22
|
+
|
|
23
|
+
There is one user-facing shape: the **Source Quiver**.
|
|
24
|
+
|
|
25
|
+
- Human-authored, git-friendly
|
|
26
|
+
- Root `Quiver.yaml`
|
|
27
|
+
- Quills under `quills/<name>/<version>/Quill.yaml`
|
|
28
|
+
- Assets in normal source layout
|
|
29
|
+
|
|
30
|
+
A build step (`Quiver.build`) derives a **runtime artifact** from the source.
|
|
31
|
+
This artifact is not a peer "format" — it is the source layout's `dist/`
|
|
32
|
+
output, optimized for browser delivery (hashed manifest, per-quill bundle
|
|
33
|
+
zips, dehydrated shared font store, stable pointer file). Authors do not
|
|
34
|
+
version it, publish it, or check it into git.
|
|
35
|
+
|
|
36
|
+
Loaders are paired 1:1 with what they load:
|
|
37
|
+
|
|
38
|
+
- `Quiver.fromPackage(specifier)` and `Quiver.fromDir(path)` load the
|
|
39
|
+
**source** layout (Node only).
|
|
40
|
+
- `Quiver.fromBuilt(url)` loads the **build output** over HTTP/HTTPS
|
|
41
|
+
(browser-safe; works in Node too).
|
|
42
|
+
|
|
43
|
+
There is no auto-detection: each loader's name commits to what it expects.
|
|
44
|
+
|
|
45
|
+
**Naming decisions:**
|
|
46
|
+
- The verb is `build()`. Pairs with `fromBuilt()` (past participle = "the
|
|
47
|
+
built one"). Avoids collision with `npm pack`, JS bundler vocabulary,
|
|
48
|
+
and the internal "bundle zips" term.
|
|
49
|
+
- Loaders for source layouts are named by **where the source lives**
|
|
50
|
+
(`Package` / `Dir`). The loader for build output is named by **what it
|
|
51
|
+
loads** (`Built`), not by transport — `fromUrl` was rejected because it
|
|
52
|
+
hides the artifact type.
|
|
53
|
+
- Build output is HTTP-served only. Loading a built artifact from local
|
|
54
|
+
disk is intentionally unsupported in V1; consumers either spin up a
|
|
55
|
+
local HTTP server or use `fromPackage`/`fromDir` against the source.
|
|
56
|
+
Adding `Quiver.fromBuiltDir(path)` later is purely additive if a real
|
|
57
|
+
use case appears.
|
|
43
58
|
|
|
44
59
|
### 2) `Quiver.yaml` Is Required in Source Quivers
|
|
45
60
|
|
|
@@ -57,32 +72,26 @@ Unknown fields in `Quiver.yaml` are a **validation error** (`quiver_invalid`). S
|
|
|
57
72
|
### 3) `QuillSource` Becomes Quiver-Centric
|
|
58
73
|
|
|
59
74
|
- Re-express `QuillSource` concepts around Quivers
|
|
60
|
-
-
|
|
61
|
-
-
|
|
62
|
-
|
|
63
|
-
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
`
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
- unqualified refs (e.g. `usaf_memo`)
|
|
81
|
-
- selector refs (e.g. `usaf_memo@1.2`)
|
|
82
|
-
|
|
83
|
-
No global highest-across-all-quivers behavior.
|
|
84
|
-
|
|
85
|
-
**Identity collision:** duplicate `Quiver.yaml.name` across composed quivers is an error in V1.
|
|
75
|
+
- Three loaders, each with one input shape and one shape-of-thing-loaded:
|
|
76
|
+
- `Quiver.fromPackage(specifier)` — Node-only; resolves an npm specifier
|
|
77
|
+
against `node_modules` and loads the source layout at the package root
|
|
78
|
+
- `Quiver.fromDir(path)` — Node-only; loads source layout from a local
|
|
79
|
+
directory. Also accepts `import.meta.url`-style `file://` URLs as a
|
|
80
|
+
convenience for tests (the URL's parent directory is used)
|
|
81
|
+
- `Quiver.fromBuilt(url)` — browser-safe; loads build output from an
|
|
82
|
+
`http(s)://` or origin-relative URL
|
|
83
|
+
- "Transport" is not a first-class concept; HTTP fetching is an internal
|
|
84
|
+
detail of `fromBuilt`
|
|
85
|
+
|
|
86
|
+
### 4) Single-Quiver Scope (V1)
|
|
87
|
+
|
|
88
|
+
V1 has no multi-quiver composition layer. Each `Quiver` instance is
|
|
89
|
+
independent: consumers load one quiver and call `resolve` / `getQuill` /
|
|
90
|
+
`warm` directly on it. There is no `QuiverRegistry`.
|
|
91
|
+
|
|
92
|
+
If composition becomes a real use case, the additive path is a thin
|
|
93
|
+
`Quiver.compose([a, b, ...])` factory that returns a quiver-shaped
|
|
94
|
+
composite — it is intentionally out of scope for V1.
|
|
86
95
|
|
|
87
96
|
### 5) Semver Selector Rules Are Strict and Small
|
|
88
97
|
|
|
@@ -108,17 +117,35 @@ Canonical version format:
|
|
|
108
117
|
|
|
109
118
|
Canonicalization:
|
|
110
119
|
|
|
111
|
-
- resolve selector to canonical ref once per
|
|
112
|
-
- key
|
|
120
|
+
- resolve selector to canonical ref once per call to `getQuill`
|
|
121
|
+
- key the tree cache by canonical ref; key the quill cache by (engine, canonical-ref)
|
|
113
122
|
|
|
114
123
|
### 6) Warm/Prefetch Is Purely a Quiver Concern
|
|
115
124
|
|
|
116
|
-
`warm()`
|
|
125
|
+
`warm()` is the network prefetch step. It fetches every quill's tree and
|
|
126
|
+
populates the per-quiver tree cache. It does **not** materialize Quill
|
|
127
|
+
instances and does **not** require an engine — `engine.quill(tree)` is
|
|
128
|
+
microseconds and runs lazily inside `getQuill`. A subsequent `getQuill`
|
|
129
|
+
reuses the cached tree, so the network fetch isn't paid twice.
|
|
130
|
+
|
|
131
|
+
Tree cache lifecycle:
|
|
117
132
|
|
|
118
|
-
- `warm()`
|
|
119
|
-
- `
|
|
133
|
+
- `warm()` populates the tree cache.
|
|
134
|
+
- First `getQuill(ref, { engine })` reads the tree, materializes the
|
|
135
|
+
Quill via `engine.quill(tree)`, then evicts the tree so its bytes can
|
|
136
|
+
be GC'd. The materialized Quill is what's kept (per engine).
|
|
137
|
+
- If `engine.quill` throws, the tree is retained so a retry skips the
|
|
138
|
+
network.
|
|
139
|
+
- Repeated `getQuill` on the same engine hits the per-engine quill cache
|
|
140
|
+
— no tree access at all.
|
|
141
|
+
- A subsequent `getQuill` for a different engine refetches the tree
|
|
142
|
+
(single-engine apps never pay this cost).
|
|
120
143
|
|
|
121
|
-
|
|
144
|
+
Other invariants:
|
|
145
|
+
|
|
146
|
+
- `resolve()` works whether or not anything is warmed.
|
|
147
|
+
- Warm semantics are identical for source-loaded and built-output-loaded
|
|
148
|
+
quivers; the loader hides the difference.
|
|
122
149
|
|
|
123
150
|
### 7) Engine Boundary: New Canonical Contract (Node / JS–WASM only)
|
|
124
151
|
|
|
@@ -140,7 +167,7 @@ For advanced dynamic-asset behavior, defer to Quillmark’s JS/WASM docs; the de
|
|
|
140
167
|
### 8) Markdown and Ref Parsing Boundary
|
|
141
168
|
|
|
142
169
|
- Markdown parsing does not require a quill registry: `Document.fromMarkdown(markdown)`
|
|
143
|
-
- Quiver owns ref parsing and selector resolution for its own API (`resolve`, `warm`, validation)
|
|
170
|
+
- Quiver owns ref parsing and selector resolution for its own API (`resolve`, `getQuill`, `warm`, validation)
|
|
144
171
|
- QUILL field is informational at render time; Quiver routes to the intended quill explicitly without mutating the parsed document in V1
|
|
145
172
|
|
|
146
173
|
Upstream behavior note:
|
|
@@ -150,16 +177,41 @@ Upstream behavior note:
|
|
|
150
177
|
|
|
151
178
|
### 9) Distribution Strategy
|
|
152
179
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
-
|
|
162
|
-
|
|
180
|
+
**Source-first distribution.** The published artifact is the **Source
|
|
181
|
+
Quiver** — an npm package whose root contains `Quiver.yaml`. Consumers
|
|
182
|
+
choose how to consume it:
|
|
183
|
+
|
|
184
|
+
- **Node consumers** load the source layout directly:
|
|
185
|
+
```ts
|
|
186
|
+
const quiver = await Quiver.fromPackage("@org/my-quiver");
|
|
187
|
+
```
|
|
188
|
+
- **Browser consumers** run a build step against the resolved source dir
|
|
189
|
+
and serve the output as static assets:
|
|
190
|
+
```ts
|
|
191
|
+
// build step (Node)
|
|
192
|
+
await Quiver.build("./node_modules/@org/my-quiver", "./public/quivers/my-quiver");
|
|
193
|
+
// browser runtime
|
|
194
|
+
const quiver = await Quiver.fromBuilt("/quivers/my-quiver/");
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Rationale:
|
|
198
|
+
|
|
199
|
+
- Author release pipeline is `npm publish` (or `git tag`). No second
|
|
200
|
+
artifact, no CDN, no hash bookkeeping outside the npm tarball.
|
|
201
|
+
- Deployment topology is the consumer's concern, not the author's.
|
|
202
|
+
- The runtime artifact is a build output of the source, not a peer
|
|
203
|
+
distribution shape (see §1).
|
|
204
|
+
|
|
205
|
+
**Pre-built distribution as a published artifact is supported but not
|
|
206
|
+
the default.** Authors who need to ship runtime-ready output directly
|
|
207
|
+
(e.g. their consumers cannot run a Node build step) may publish
|
|
208
|
+
`Quiver.build(...)` output to a CDN and instruct consumers to use
|
|
209
|
+
`Quiver.fromBuilt(<cdn-url>)`. Treated as the exception.
|
|
210
|
+
|
|
211
|
+
Validation responsibility shifts left: authors should run
|
|
212
|
+
`Quiver.fromDir` and `Quiver.build` in CI so `quiver_invalid` errors
|
|
213
|
+
surface on publish, not on the consumer's build. The bundled
|
|
214
|
+
`@quillmark/quiver/testing` harness covers this.
|
|
163
215
|
|
|
164
216
|
---
|
|
165
217
|
|
|
@@ -167,19 +219,20 @@ Clarification:
|
|
|
167
219
|
|
|
168
220
|
V1 intentionally retains:
|
|
169
221
|
|
|
170
|
-
1. Font dehydration as a
|
|
171
|
-
2. Consumer validation tooling for
|
|
172
|
-
3. Manifest pointer resolution for
|
|
173
|
-
4. HTTP loading
|
|
174
|
-
5. Source
|
|
175
|
-
6.
|
|
176
|
-
7.
|
|
177
|
-
8.
|
|
178
|
-
9. Preload/fail-fast helpers where they still add value
|
|
222
|
+
1. Font dehydration as a build-output property
|
|
223
|
+
2. Consumer validation tooling for source layouts (+ optional build-parity checks)
|
|
224
|
+
3. Manifest pointer resolution for build output
|
|
225
|
+
4. HTTP/HTTPS loading via `Quiver.fromBuilt`
|
|
226
|
+
5. Source layout loading as first-class dev loop (`fromPackage` / `fromDir`)
|
|
227
|
+
6. Typed errors (`QuiverError`) with quiver/source context
|
|
228
|
+
7. Concurrency coalescing for in-flight loads
|
|
229
|
+
8. Preload/fail-fast helpers where they still add value
|
|
179
230
|
|
|
180
231
|
Removed from carryover assumptions:
|
|
181
232
|
|
|
182
233
|
- Any engine-registration cache fast path (`register`/`has`) because upstream removed the capability
|
|
234
|
+
- "Transport" as a user-facing concept (folded into `fromBuilt` internals)
|
|
235
|
+
- Loading build output from local disk (no `fromBuiltDir` in V1; YAGNI)
|
|
183
236
|
|
|
184
237
|
---
|
|
185
238
|
|
|
@@ -188,7 +241,7 @@ Removed from carryover assumptions:
|
|
|
188
241
|
V1 should trim public API where behavior can stay internal:
|
|
189
242
|
|
|
190
243
|
- Drop internal-only utility exports
|
|
191
|
-
- Keep engine payload and
|
|
244
|
+
- Keep engine payload and loader internals opaque
|
|
192
245
|
- Consolidate validation exports
|
|
193
246
|
- Avoid duplicate entry points for equivalent validation workflows
|
|
194
247
|
- Do not expose internal quill-object cache mechanisms as public contract
|
|
@@ -204,13 +257,12 @@ V1 code catalog (closed set):
|
|
|
204
257
|
| Code | Fires when |
|
|
205
258
|
|---|---|
|
|
206
259
|
| `invalid_ref` | Malformed ref string at `resolve()`/`warm()` boundary (fails `parseQuillRef`) |
|
|
207
|
-
| `quill_not_found` | Selector did not match any quill in
|
|
260
|
+
| `quill_not_found` | Selector did not match any quill in the quiver |
|
|
208
261
|
| `quiver_invalid` | `Quiver.yaml` or hashed manifest malformed, unknown field, non-canonical version on disk, or font/bundle hash mismatch |
|
|
209
262
|
| `transport_error` | I/O failure: missing path, HTTP non-2xx, network error, permission error. Wraps underlying cause. |
|
|
210
|
-
| `quiver_collision` | Two composed quivers share `Quiver.yaml.name` at registry construction |
|
|
211
263
|
|
|
212
264
|
Notes:
|
|
213
|
-
- `quill_not_found` is selector-resolution failure
|
|
265
|
+
- `quill_not_found` is selector-resolution failure within a quiver's catalog.
|
|
214
266
|
- `transport_error` is artifact access failure (filesystem/HTTP/network/permissions), including missing packed files and HTTP 404.
|
|
215
267
|
- Legacy categories such as `manifest_invalid`, `quill_load_failed`, and `backend_not_found` are folded into `quiver_invalid` or `transport_error` in V1.
|
|
216
268
|
|
|
@@ -218,18 +270,22 @@ Errors must include offending ref/version/quiver identifiers when available.
|
|
|
218
270
|
|
|
219
271
|
---
|
|
220
272
|
|
|
221
|
-
## Runtime +
|
|
273
|
+
## Runtime + Build Model
|
|
222
274
|
|
|
223
275
|
V1 runtime loading paths:
|
|
224
276
|
|
|
225
|
-
1.
|
|
226
|
-
|
|
227
|
-
|
|
277
|
+
1. `Quiver.fromPackage(specifier)` — npm package resolution; loads source
|
|
278
|
+
(authoring/dev/Node runtime)
|
|
279
|
+
2. `Quiver.fromDir(path)` — local directory; loads source (Node)
|
|
280
|
+
3. `Quiver.fromBuilt(url)` — HTTP(S); loads build output (browser; also
|
|
281
|
+
works in Node)
|
|
228
282
|
|
|
229
|
-
V1
|
|
283
|
+
V1 build behavior:
|
|
230
284
|
|
|
231
|
-
- `Quiver.
|
|
232
|
-
|
|
285
|
+
- `Quiver.build(srcDir, outDir)` produces the runtime artifact from a
|
|
286
|
+
source layout
|
|
287
|
+
- output includes pointer + hashed manifest + bundles + dehydrated font store
|
|
288
|
+
(see "Runtime Artifact Format" below)
|
|
233
289
|
|
|
234
290
|
Execution behavior:
|
|
235
291
|
|
|
@@ -261,7 +317,11 @@ Caching scope:
|
|
|
261
317
|
- Non-canonical version directories are a validation error (`quiver_invalid`).
|
|
262
318
|
- Dedup of identical fonts across quills happens at pack time (into `store/<md5>`), not at the source layer.
|
|
263
319
|
|
|
264
|
-
##
|
|
320
|
+
## Runtime Artifact Format (normative)
|
|
321
|
+
|
|
322
|
+
Produced by `Quiver.build()`. Authors do not author this layout; consumers
|
|
323
|
+
do not need to inspect it. It is an implementation detail of build output,
|
|
324
|
+
specified here only because loaders must agree on its shape.
|
|
265
325
|
|
|
266
326
|
```
|
|
267
327
|
<root>/
|
|
@@ -272,7 +332,7 @@ Caching scope:
|
|
|
272
332
|
<md5> # raw font bytes, no extension
|
|
273
333
|
```
|
|
274
334
|
|
|
275
|
-
**Hash:** MD5 prefix-6, computed with `node:crypto` at `
|
|
335
|
+
**Hash:** MD5 prefix-6, computed with `node:crypto` at `build()` time only (dev/tooling; not browser runtime).
|
|
276
336
|
|
|
277
337
|
**Pointer** `Quiver.json`:
|
|
278
338
|
```json
|
|
@@ -295,63 +355,71 @@ Caching scope:
|
|
|
295
355
|
}
|
|
296
356
|
```
|
|
297
357
|
|
|
298
|
-
**Bundle zips** contain pure quill content (`Quill.yaml` + templates + partials + non-font assets). Fonts are dehydrated at
|
|
358
|
+
**Bundle zips** contain pure quill content (`Quill.yaml` + templates + partials + non-font assets). Fonts are dehydrated at build time: their bytes live only in `store/<md5>`; their path→hash mapping lives only in the hashed manifest. Bundles do **not** embed a `fonts.json`.
|
|
299
359
|
|
|
300
|
-
Rehydration on load:
|
|
360
|
+
Rehydration on load: the loader fetches the pointer → hashed manifest → required bundle(s) → required `store/<md5>` blobs; library reconstructs the full in-memory tree and builds a render-ready quill via `engine.quill(tree)`.
|
|
301
361
|
|
|
302
362
|
## API Surface (V1)
|
|
303
363
|
|
|
304
|
-
Single class, three
|
|
364
|
+
Single class, three loaders + one builder. Each loader has one input
|
|
365
|
+
shape and one shape-of-thing-loaded; no auto-detection.
|
|
305
366
|
|
|
306
367
|
```ts
|
|
307
368
|
class Quiver {
|
|
308
|
-
// Node-only
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
// Node-only
|
|
314
|
-
|
|
369
|
+
// Node-only loader: resolve an npm specifier against node_modules and
|
|
370
|
+
// load the source layout at the package root.
|
|
371
|
+
// (From `@quillmark/quiver/node`.)
|
|
372
|
+
static fromPackage(specifier: string): Promise<Quiver>;
|
|
373
|
+
|
|
374
|
+
// Node-only loader: load source layout from a local directory.
|
|
375
|
+
// Also accepts `import.meta.url`-style `file://` URLs (the URL's parent
|
|
376
|
+
// directory is used) as a convenience for tests.
|
|
377
|
+
// (From `@quillmark/quiver/node`.)
|
|
378
|
+
static fromDir(pathOrFileUrl: string): Promise<Quiver>;
|
|
379
|
+
|
|
380
|
+
// Browser-safe loader: load build output from an http(s):// or
|
|
381
|
+
// origin-relative URL. Throws `transport_error` on file:// inputs.
|
|
382
|
+
// (From `@quillmark/quiver` main.)
|
|
383
|
+
static fromBuilt(url: string): Promise<Quiver>;
|
|
384
|
+
|
|
385
|
+
// Node-only tooling: produce the runtime artifact from a source layout.
|
|
386
|
+
// (From `@quillmark/quiver/node`.)
|
|
387
|
+
static build(sourceDir: string, outDir: string, opts?: BuildOptions): Promise<void>;
|
|
315
388
|
|
|
316
389
|
readonly name: string; // from Quiver.yaml
|
|
317
390
|
|
|
318
|
-
// Read-only introspection and lazy tree access used by
|
|
319
|
-
//
|
|
391
|
+
// Read-only introspection and lazy tree access; also used internally by
|
|
392
|
+
// resolve/getQuill/warm.
|
|
320
393
|
quillNames(): string[]; // sorted lex
|
|
321
394
|
versionsOf(name: string): string[]; // sorted desc
|
|
322
395
|
loadTree(name: string, version: string): Promise<Map<string, Uint8Array>>;
|
|
323
|
-
}
|
|
324
|
-
```
|
|
325
|
-
|
|
326
|
-
```ts
|
|
327
|
-
class QuiverRegistry {
|
|
328
|
-
constructor(args: { engine: Quillmark; quivers: Quiver[] });
|
|
329
396
|
|
|
330
397
|
// Selector ref -> canonical ref. Throws invalid_ref / quill_not_found.
|
|
331
398
|
resolve(ref: string): Promise<string>;
|
|
332
399
|
|
|
333
|
-
//
|
|
334
|
-
|
|
400
|
+
// Selector or canonical ref -> render-ready quill handle (materialized via
|
|
401
|
+
// engine.quill(tree), cached per (engine, canonical-ref)).
|
|
402
|
+
getQuill(ref: string, opts: { engine: Quillmark }): Promise<Quill>;
|
|
335
403
|
|
|
336
|
-
//
|
|
404
|
+
// Prefetches every quill tree (network-only; engine not required).
|
|
405
|
+
// Subsequent getQuill calls reuse the cached tree. Fail-fast.
|
|
337
406
|
warm(): Promise<void>;
|
|
338
407
|
}
|
|
339
408
|
|
|
340
409
|
class QuiverError extends Error {
|
|
341
|
-
code: "invalid_ref" | "quill_not_found" | "quiver_invalid" | "transport_error"
|
|
410
|
+
code: "invalid_ref" | "quill_not_found" | "quiver_invalid" | "transport_error";
|
|
342
411
|
// plus contextual payload fields
|
|
343
412
|
}
|
|
344
413
|
```
|
|
345
414
|
|
|
346
|
-
**No render wrapper.** Callers invoke `quill.render(doc, opts?)` (and `quill.open(doc)` when needed) after `
|
|
415
|
+
**No render wrapper.** Callers invoke `quill.render(doc, opts?)` (and `quill.open(doc)` when needed) after `getQuill()`. Quiver never mirrors Quillmark render APIs.
|
|
347
416
|
|
|
348
|
-
**Internal (not exported):** `
|
|
417
|
+
**Internal (not exported):** `QuiverManifest` (runtime shape), `parseQuillRef`, in-flight coalescing state, source-vs-built layout detection.
|
|
349
418
|
|
|
350
419
|
Hot-path flow:
|
|
351
420
|
```ts
|
|
352
421
|
const doc = Document.fromMarkdown(md);
|
|
353
|
-
const
|
|
354
|
-
const quill = await registry.getQuill(canonicalRef);
|
|
422
|
+
const quill = await quiver.getQuill(doc.quillRef, { engine });
|
|
355
423
|
const result = quill.render(doc, { format: "pdf" });
|
|
356
424
|
```
|
|
357
425
|
|
|
@@ -360,13 +428,23 @@ const result = quill.render(doc, { format: "pdf" });
|
|
|
360
428
|
**Name:** `@quillmark/quiver`
|
|
361
429
|
|
|
362
430
|
**Entrypoints:**
|
|
363
|
-
- `@quillmark/quiver` (main, browser-safe): `Quiver` class with only
|
|
364
|
-
|
|
431
|
+
- `@quillmark/quiver` (main, browser-safe): `Quiver` class with only
|
|
432
|
+
`fromBuilt` functional (Node-only loaders/builder throw
|
|
433
|
+
`transport_error` if reached in browser), `QuiverError`,
|
|
434
|
+
`QuillmarkLike`, `QuillLike`, shared types.
|
|
435
|
+
- `@quillmark/quiver/node`: adds `Quiver.fromPackage`, `Quiver.fromDir`,
|
|
436
|
+
`Quiver.build` behaviors. Single `Quiver` class — Node-only factories
|
|
437
|
+
fail fast outside Node.
|
|
438
|
+
- `@quillmark/quiver/testing` (Node-only): single export
|
|
439
|
+
`runQuiverTests(metaUrlOrDir, engine)` built on `node:test` (zero
|
|
440
|
+
external test-runner dependency). Optional convenience; users on other
|
|
441
|
+
test runners wire their own loops against the main API.
|
|
365
442
|
|
|
366
443
|
**Dependencies:**
|
|
367
444
|
- Peer: `@quillmark/wasm@>=0.59.0-rc.2` with `Quillmark`, `Document.fromMarkdown`, `engine.quill(tree)`, and `quill.render(doc, opts?)` APIs.
|
|
368
445
|
- Runtime: `fflate ^0.8.2` for zip read/write (Node + browser)
|
|
369
|
-
- Dev-only: `node:crypto` (MD5 hashing in `
|
|
446
|
+
- Dev-only: `node:crypto` (MD5 hashing in `build()` — never reached at runtime)
|
|
447
|
+
- No test-runner peer dependency; `/testing` uses `node:test` (built-in)
|
|
370
448
|
|
|
371
449
|
---
|
|
372
450
|
|
|
@@ -380,7 +458,7 @@ const result = quill.render(doc, { format: "pdf" });
|
|
|
380
458
|
- inter-quiver dependency graph in `Quiver.yaml`
|
|
381
459
|
- marketplace/discovery service
|
|
382
460
|
- advanced warm strategies beyond API-compatible hooks
|
|
383
|
-
- multi-quiver
|
|
461
|
+
- multi-quiver composition (single quiver per consumer in V1)
|
|
384
462
|
|
|
385
463
|
---
|
|
386
464
|
|
|
@@ -388,14 +466,14 @@ const result = quill.render(doc, { format: "pdf" });
|
|
|
388
466
|
|
|
389
467
|
All V1 planner questions resolved; implementation plan can proceed against the spec above.
|
|
390
468
|
|
|
391
|
-
1. ~~Final `Quiver` interface shape and transport factoring style~~ → Single `Quiver` class, three
|
|
469
|
+
1. ~~Final `Quiver` interface shape and transport factoring style~~ → Single `Quiver` class, three loaders (`fromPackage`, `fromDir`, `fromBuilt`) + one builder (`build`). Each loader names what it loads; no auto-detection.
|
|
392
470
|
2. ~~Final `Quiver.yaml` schema and unknown-field policy~~ → See §2: alphanumeric `name` and optional tooling-only `description`. Unknown fields are `quiver_invalid`.
|
|
393
471
|
3. ~~Canonical ref grammar and parser API contract~~ → Internal `parseQuillRef`, not exported. Selector syntax per §5. Throws `invalid_ref`.
|
|
394
|
-
4. ~~Exact warning policy for shadowed refs across quivers~~ →
|
|
395
|
-
5. ~~Validation API shape consolidation~~ → No separate validation API. Validation errors surface as `QuiverError('quiver_invalid')` during load or `
|
|
396
|
-
6. ~~
|
|
472
|
+
4. ~~Exact warning policy for shadowed refs across quivers~~ → N/A in V1: no multi-quiver composition layer; each `Quiver` instance is independent (§4).
|
|
473
|
+
5. ~~Validation API shape consolidation~~ → No separate validation API. Validation errors surface as `QuiverError('quiver_invalid')` during load or `build()`.
|
|
474
|
+
6. ~~Build output directory structure~~ → See "Runtime Artifact Format (normative)".
|
|
397
475
|
7. ~~Node/browser entrypoint split~~ → See "Package Structure": main + `/node` subpath, single `Quiver` class.
|
|
398
|
-
8. ~~Final exported type names~~ → `Quiver`, `
|
|
476
|
+
8. ~~Final exported type names~~ → `Quiver`, `QuiverError`. Hot-path entry is `Quiver.getQuill(ref, { engine })`.
|
|
399
477
|
|
|
400
478
|
---
|
|
401
479
|
|
|
@@ -413,7 +491,8 @@ Local copies in this repo for `@quillmark/quiver` implementation:
|
|
|
413
491
|
## Success Criteria
|
|
414
492
|
|
|
415
493
|
- A team can author and validate a Source Quiver locally with fast filesystem loops
|
|
416
|
-
-
|
|
494
|
+
- Build output can be loaded over HTTP/HTTPS with parity behavior in browser and Node
|
|
495
|
+
- Each loader names exactly what it loads — no auto-detection, no ambiguous return shape
|
|
417
496
|
- Multi-quiver resolution is deterministic and matches precedence hard-filter rules
|
|
418
497
|
- Selector behavior is predictable and explicitly documented
|
|
419
498
|
- Quiver (Node) integrates via `engine.quill(tree)` + `quill.render(...)` only (no engine quill registration path)
|