@quillmark/wasm 0.123.1 → 0.125.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.
@@ -162,11 +162,11 @@ function quillmarkError(code, message, hint) {
162
162
  }
163
163
 
164
164
  /**
165
- * Throw on options that are not a plain object, or on an own key of them
166
- * outside `known`. The JS twin of the binding's `reject_unknown_keys`, in the
167
- * uncoded shape it throws: a misspelled optional key would otherwise read as
168
- * absent, and a read reaches a key up the prototype chain that no own-key walk
169
- * sees.
165
+ * Throw on options that are not a plain object, or on a key of them, or of a
166
+ * null-prototype object they inherit from, outside `known`. The JS twin of the
167
+ * binding's `reject_unknown_keys`, in the uncoded shape it throws: a misspelled
168
+ * optional key would otherwise read as absent, and a read reaches a key up the
169
+ * prototype chain that no own-key walk sees.
170
170
  * @param {unknown} options
171
171
  * @param {string[]} known
172
172
  * @param {string} what the options' name, for the message
@@ -174,8 +174,28 @@ function quillmarkError(code, message, hint) {
174
174
  */
175
175
  function rejectUnknownKeys(options, known, what) {
176
176
  if (options == null) return;
177
- const proto = Object.getPrototypeOf(options);
178
177
  let message;
178
+ try {
179
+ message = unknownKeyRefusal(options, known, what);
180
+ } catch (e) {
181
+ const cause = e instanceof Error ? e.message : String(e);
182
+ message = `${what} must be a plain object, not one whose read throws: ${cause}`;
183
+ }
184
+ if (message === undefined) return;
185
+ const err = /** @type {any} */ (new Error(message));
186
+ err.diagnostics = [{ severity: 'error', message }];
187
+ throw err;
188
+ }
189
+
190
+ /**
191
+ * The refusal `rejectUnknownKeys` throws, if any. A `Proxy` trap may throw.
192
+ * @param {unknown} options
193
+ * @param {string[]} known
194
+ * @param {string} what
195
+ * @returns {string | undefined}
196
+ */
197
+ function unknownKeyRefusal(options, known, what) {
198
+ const proto = Object.getPrototypeOf(options);
179
199
  if (proto !== null && Object.getPrototypeOf(proto) !== null) {
180
200
  const passed =
181
201
  options instanceof Map
@@ -183,15 +203,16 @@ function rejectUnknownKeys(options, known, what) {
183
203
  : typeof options === 'object'
184
204
  ? 'one inheriting from another object'
185
205
  : `a ${typeof options}`;
186
- message = `${what} must be a plain object, not ${passed}`;
187
- } else {
188
- const key = Object.getOwnPropertyNames(options).find((k) => !known.includes(k));
189
- if (key === undefined) return;
190
- message = `${what} have unknown key \`${key}\`; ${what} take only \`${known.join('`, `')}\``;
206
+ return `${what} must be a plain object, not ${passed}`;
191
207
  }
192
- const err = /** @type {any} */ (new Error(message));
193
- err.diagnostics = [{ severity: 'error', message }];
194
- throw err;
208
+ const keys = Object.getOwnPropertyNames(options);
209
+ if (proto !== null) {
210
+ const builtin = Object.getOwnPropertyNames(Object.prototype);
211
+ keys.push(...Object.getOwnPropertyNames(proto).filter((k) => !builtin.includes(k)));
212
+ }
213
+ const key = keys.find((k) => !known.includes(k));
214
+ if (key === undefined) return undefined;
215
+ return `${what} have unknown key \`${key}\`; ${what} take only \`${known.join('`, `')}\``;
195
216
  }
196
217
 
197
218
  // These checks deliver the ERROR, not the rejection, at the seams that cross
@@ -263,9 +284,10 @@ function requireLocalQuill(quill, method) {
263
284
  // `runtime.test.js` § "container run boundaries" stamps a pair and re-imports
264
285
  // it, where `Content::normalize` re-mints against the Rust predicate. A tag the
265
286
  // table omits welds with nothing, so `tests/known_names_drift.rs` pins that
266
- // every container has an entry.
287
+ // every container has an entry. An entry of `'*'` welds on the whole bag: an
288
+ // element's attributes are open, its name among them.
267
289
 
268
- const WELD_KEYS = { list_item: ['ordered'], quote: [] };
290
+ const WELD_KEYS = { list_item: ['ordered'], quote: [], element: '*' };
269
291
 
270
292
  /**
271
293
  * @param {import('../core/wasm.js').ContentContainer} a
@@ -278,7 +300,20 @@ function weldsWith(a, b) {
278
300
  // `hasOwn`, so a tag colliding with an `Object.prototype` member answers
279
301
  // `false` rather than reaching a function.
280
302
  if (!Object.hasOwn(WELD_KEYS, a.container)) return false;
281
- return WELD_KEYS[a.container].every((k) => a.attrs?.[k] === b.attrs?.[k]);
303
+ const keys = WELD_KEYS[a.container];
304
+ if (keys === '*') return sameBag(a.attrs, b.attrs);
305
+ return keys.every((k) => a.attrs?.[k] === b.attrs?.[k]);
306
+ }
307
+
308
+ /**
309
+ * @param {Record<string, unknown> | undefined} a
310
+ * @param {Record<string, unknown> | undefined} b
311
+ * @returns {boolean}
312
+ */
313
+ function sameBag(a, b) {
314
+ const [x, y] = [a ?? {}, b ?? {}];
315
+ const keys = Object.keys(x);
316
+ return keys.length === Object.keys(y).length && keys.every((k) => Object.hasOwn(y, k) && x[k] === y[k]);
282
317
  }
283
318
 
284
319
  /**
@@ -510,16 +545,11 @@ export class Engine {
510
545
  * memory and run `fn` against the backend engine. Only `render`/`open` call
511
546
  * this, so `doc` is always present.
512
547
  *
513
- * OWNERSHIP WINDOW: both caller handles are snapshotted (`doc.toStored()` and
514
- * `doc.warnings`, and `quill.toTree()` on a clone-cache miss) BEFORE the first
515
- * await. The backend load below is a real suspension point, so reading the
548
+ * OWNERSHIP WINDOW: both caller handles are snapshotted (`doc.toStored()`,
549
+ * and `quill.toTree()` on a clone-cache miss) BEFORE the first await. The backend load below is a real suspension point, so reading the
516
550
  * handles after it would race a caller that `free()`s them as soon as this
517
551
  * call returns its promise ("null pointer passed to rust").
518
552
  *
519
- * `docWarnings` rides the context because the storage DTO does not carry
520
- * them: `fromStored` clears the load's warnings, so the backend clone knows
521
- * nothing of them and `render` splices the snapshot back in.
522
- *
523
553
  * Clone lifetimes differ by design: the `doc` clone is TRANSIENT, freed in
524
554
  * the `finally` of every call, while the `quill` clone is CACHED and is not
525
555
  * freed here — a `Quill` instance's contents never change after
@@ -528,13 +558,12 @@ export class Engine {
528
558
  * @param {string} method the caller's name, for the rejection message
529
559
  * @param {Quill} quill
530
560
  * @param {Document} doc
531
- * @param {(ctx: { mod: any, engine: any, quill: any, doc: any, docWarnings: any[] }) => any} fn
561
+ * @param {(ctx: { mod: any, engine: any, quill: any, doc: any }) => any} fn
532
562
  */
533
563
  async #withClones(method, quill, doc, fn) {
534
564
  const backendId = this.#backendOf(quill, method);
535
565
  requireLocalDoc(doc, method);
536
566
  const docJson = doc.toStored();
537
- const docWarnings = doc.warnings;
538
567
  const quillTree = this.#quillClones.get(this.#descriptorFor(backendId).load)?.has(quill)
539
568
  ? null
540
569
  : quill.toTree();
@@ -546,7 +575,7 @@ export class Engine {
546
575
  let backendDoc = null;
547
576
  try {
548
577
  backendDoc = mod.Document.fromStored(docJson);
549
- return fn({ mod, engine, quill: backendQuill, doc: backendDoc, docWarnings });
578
+ return fn({ mod, engine, quill: backendQuill, doc: backendDoc });
550
579
  } finally {
551
580
  backendDoc?.free();
552
581
  }
@@ -564,11 +593,7 @@ export class Engine {
564
593
  'engine.render(quill, doc)',
565
594
  quill,
566
595
  doc,
567
- ({ engine, quill: q, doc: d, docWarnings }) => {
568
- const result = engine.render(q, d, options ?? undefined, today);
569
- result.warnings = docWarnings.concat(result.warnings);
570
- return result;
571
- }
596
+ ({ engine, quill: q, doc: d }) => engine.render(q, d, options ?? undefined, today)
572
597
  );
573
598
  }
574
599
 
@@ -668,6 +693,13 @@ export class LiveSession {
668
693
  return this.#inner.fieldBoxes(field);
669
694
  }
670
695
 
696
+ /**
697
+ * @returns {import('./runtime.d.ts').FieldCoverage[]}
698
+ */
699
+ coverage() {
700
+ return this.#inner.coverage();
701
+ }
702
+
671
703
  /**
672
704
  * @param {number} page
673
705
  * @param {number} x
@@ -742,21 +774,21 @@ export class DocumentWriter {
742
774
  /**
743
775
  * @param {string} name
744
776
  * @param {unknown} value
745
- * @returns {void}
777
+ * @returns {import('../core/wasm.js').Diagnostic[]}
746
778
  */
747
779
  set(name, value) {
748
780
  return this.#doc._commitField(this.#quill, name, value);
749
781
  }
750
782
  /**
751
783
  * @param {Record<string, unknown>} fields
752
- * @returns {void}
784
+ * @returns {import('../core/wasm.js').Diagnostic[]}
753
785
  */
754
786
  setAll(fields) {
755
787
  return this.#doc._commitFields(this.#quill, MAIN_CARD_ADDR, fields);
756
788
  }
757
789
  /**
758
790
  * @param {string} markdown
759
- * @returns {import('../core/wasm.js').Delta}
791
+ * @returns {import('../core/wasm.js').Revised}
760
792
  */
761
793
  reviseBody(markdown) {
762
794
  return this.#doc.revise({}, markdown);
@@ -764,17 +796,24 @@ export class DocumentWriter {
764
796
  /**
765
797
  * @param {string} name
766
798
  * @param {string} text
767
- * @returns {import('../core/wasm.js').Delta}
799
+ * @returns {import('../core/wasm.js').Revised}
768
800
  */
769
801
  reviseField(name, text) {
770
802
  return this.#doc._reviseField(this.#quill, name, text);
771
803
  }
804
+ /**
805
+ * @param {string} markdown
806
+ * @returns {import('../core/wasm.js').DocumentRevised}
807
+ */
808
+ reviseDocument(markdown) {
809
+ return this.#doc._reviseDocument(this.#quill, markdown);
810
+ }
772
811
  /**
773
812
  * @param {string} kind
774
813
  * @param {Record<string, unknown>} [fields]
775
814
  * @param {string} [body]
776
815
  * @param {number} [at] insertion index; appends when omitted
777
- * @returns {void}
816
+ * @returns {import('../core/wasm.js').Diagnostic[]}
778
817
  */
779
818
  addCard(kind, fields, body, at) {
780
819
  return this.#doc._addCard(this.#quill, kind, fields, body, at);
@@ -823,21 +862,21 @@ export class CardWriter {
823
862
  /**
824
863
  * @param {string} name
825
864
  * @param {unknown} value
826
- * @returns {void}
865
+ * @returns {import('../core/wasm.js').Diagnostic[]}
827
866
  */
828
867
  set(name, value) {
829
868
  return this.#doc._commitField(this.#quill, { card: this.#index, field: name }, value);
830
869
  }
831
870
  /**
832
871
  * @param {Record<string, unknown>} fields
833
- * @returns {void}
872
+ * @returns {import('../core/wasm.js').Diagnostic[]}
834
873
  */
835
874
  setAll(fields) {
836
875
  return this.#doc._commitFields(this.#quill, { card: this.#index }, fields);
837
876
  }
838
877
  /**
839
878
  * @param {string} markdown
840
- * @returns {import('../core/wasm.js').Delta}
879
+ * @returns {import('../core/wasm.js').Revised}
841
880
  */
842
881
  reviseBody(markdown) {
843
882
  return this.#doc.revise({ card: this.#index }, markdown);
@@ -845,7 +884,7 @@ export class CardWriter {
845
884
  /**
846
885
  * @param {string} name
847
886
  * @param {string} text
848
- * @returns {import('../core/wasm.js').Delta}
887
+ * @returns {import('../core/wasm.js').Revised}
849
888
  */
850
889
  reviseField(name, text) {
851
890
  return this.#doc._reviseField(this.#quill, { card: this.#index, field: name }, text);