@mocanvas/store 4.0.1 → 4.1.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/dist/index.d.ts CHANGED
@@ -816,11 +816,21 @@ declare class StoreSchema<R extends UnknownRecord, Props = unknown> {
816
816
  /** Scope of a record type; unknown types are treated as `document`. */
817
817
  getScope(typeName: string): RecordScope;
818
818
  /**
819
- * Validate a record, delegating to its record type's validator. Records of
820
- * unknown types are passed through untouched so that foreign data survives
821
- * a load/save round-trip.
819
+ * Validate a record, delegating to its record type's validator.
820
+ *
821
+ * Two things are checked before the delegation. First, the value has to be a
822
+ * record at all: an object with a string `id` and a string `typeName`.
823
+ * Without that check a value carrying no `typeName` looks up `undefined` in
824
+ * the type map, misses, and takes the unknown-type path below — which is how
825
+ * `store.put([{ id: "shape:bogus", x: 0, y: 0 }])` used to be accepted.
826
+ *
827
+ * Second, a record whose `typeName` this schema does not know is passed
828
+ * through untouched, so foreign data survives a load/save round-trip. That
829
+ * is the escape hatch; it is not meant to cover malformed input, hence the
830
+ * first check.
822
831
  */
823
832
  validateRecord(store: Store<R, any>, record: R, phase: StoreValidationPhase, recordBefore: R | undefined): R;
833
+ private onFailure;
824
834
  /** The current version of every sequence. Always the v2 shape — mocanvas never writes v1. */
825
835
  serialize(): SerializedSchemaV2;
826
836
  /** A schema at version 0 of every sequence (all migrations still pending). */
package/dist/index.js CHANGED
@@ -435,28 +435,51 @@ var StoreSchema = class _StoreSchema {
435
435
  return this.typeByName.get(typeName)?.scope ?? "document";
436
436
  }
437
437
  /**
438
- * Validate a record, delegating to its record type's validator. Records of
439
- * unknown types are passed through untouched so that foreign data survives
440
- * a load/save round-trip.
438
+ * Validate a record, delegating to its record type's validator.
439
+ *
440
+ * Two things are checked before the delegation. First, the value has to be a
441
+ * record at all: an object with a string `id` and a string `typeName`.
442
+ * Without that check a value carrying no `typeName` looks up `undefined` in
443
+ * the type map, misses, and takes the unknown-type path below — which is how
444
+ * `store.put([{ id: "shape:bogus", x: 0, y: 0 }])` used to be accepted.
445
+ *
446
+ * Second, a record whose `typeName` this schema does not know is passed
447
+ * through untouched, so foreign data survives a load/save round-trip. That
448
+ * is the escape hatch; it is not meant to cover malformed input, hence the
449
+ * first check.
441
450
  */
442
451
  validateRecord(store, record, phase, recordBefore) {
452
+ if (!isRecordLike(record)) {
453
+ return this.onFailure(
454
+ new Error(
455
+ `Expected a record with a string \`id\` and \`typeName\`, got ${describeRecord(record)}`
456
+ ),
457
+ store,
458
+ record,
459
+ phase,
460
+ recordBefore
461
+ );
462
+ }
443
463
  const type = this.typeByName.get(record.typeName);
444
464
  if (!type) return record;
445
465
  try {
446
466
  return type.validate(record, recordBefore);
447
467
  } catch (error) {
448
- if (this.options.onValidationFailure) {
449
- return this.options.onValidationFailure({
450
- error,
451
- store,
452
- record,
453
- phase,
454
- recordBefore: recordBefore ?? null
455
- });
456
- }
457
- throw error;
468
+ return this.onFailure(error, store, record, phase, recordBefore);
458
469
  }
459
470
  }
471
+ onFailure(error, store, record, phase, recordBefore) {
472
+ if (this.options.onValidationFailure) {
473
+ return this.options.onValidationFailure({
474
+ error,
475
+ store,
476
+ record,
477
+ phase,
478
+ recordBefore: recordBefore ?? null
479
+ });
480
+ }
481
+ throw error;
482
+ }
460
483
  /** The current version of every sequence. Always the v2 shape — mocanvas never writes v1. */
461
484
  serialize() {
462
485
  const sequences = {};
@@ -570,6 +593,18 @@ var StoreSchema = class _StoreSchema {
570
593
  return { type: "success", value: store };
571
594
  }
572
595
  };
596
+ function describeRecord(value) {
597
+ if (value === null) return "null";
598
+ if (typeof value !== "object") return typeof value;
599
+ if (Array.isArray(value)) return "an array";
600
+ const { id, typeName } = value;
601
+ const parts = [];
602
+ parts.push(typeof id === "string" ? `id ${JSON.stringify(id)}` : `id ${id === void 0 ? "missing" : typeof id}`);
603
+ parts.push(
604
+ typeof typeName === "string" ? `typeName ${JSON.stringify(typeName)}` : `typeName ${typeName === void 0 ? "missing" : typeof typeName}`
605
+ );
606
+ return `an object with ${parts.join(" and ")}`;
607
+ }
573
608
 
574
609
  // src/query.ts
575
610
  function matchesQueryValue(matcher, value) {