js-bao 0.5.0 → 0.5.2

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.
@@ -31,8 +31,32 @@ interface FieldOptions {
31
31
  unique?: boolean;
32
32
  default?: any | (() => any);
33
33
  autoAssign?: boolean;
34
+ /**
35
+ * Auto-timestamp this field with `Date.now()` (milliseconds) on save.
36
+ *
37
+ * - `'create'` — stamp only on the first save (when `isNew === true`).
38
+ * - `'update'` — stamp on every save (create AND update).
39
+ * - `'both'` — stamp on every save (create AND update).
40
+ *
41
+ * If the caller passes an explicit value for the field in `data`, the
42
+ * explicit value wins and the stamp is skipped. The stamp is applied
43
+ * in `beforeSave` BEFORE any user-defined hooks, so user hooks may
44
+ * still overwrite the value if they wish (last writer wins).
45
+ */
46
+ autoStamp?: "create" | "update" | "both";
34
47
  maxLength?: number;
35
48
  maxCount?: number;
49
+ /**
50
+ * Allowed-value set for a `string` field. When present, the codegen
51
+ * generators (database-type codegen + doc-model v2 codegen) emit a
52
+ * TypeScript string-literal union (`"a" | "b" | "c"`) instead of a bare
53
+ * `string` for this field.
54
+ *
55
+ * Advisory / codegen-only: this is a TS-emission hint. The runtime and the
56
+ * server do NOT enforce enum membership on write (see #843). Only valid on
57
+ * `string` fields, and must be a non-empty array of strings.
58
+ */
59
+ enum?: string[];
36
60
  }
37
61
  interface UniqueConstraintConfig {
38
62
  name: string;
@@ -495,6 +519,17 @@ declare class BaseModel implements StringSetChangeTracker {
495
519
  private ensureLocalChanges;
496
520
  private hasLocalChange;
497
521
  private getFromYjs;
522
+ /**
523
+ * Defensive normalization for the read/sync boundary (issue #625).
524
+ *
525
+ * Stringset fields are owned by getStringSetFromYjs (which expects the
526
+ * raw Y.Map / legacy plain-object shape — #561 / #601), so they pass
527
+ * through untouched. For any other field, a composite Yjs primitive
528
+ * (Y.Map / Y.Array / Y.Text) must never reach user code or the DB layer
529
+ * raw: normalize it to its plain-JS projection (toJSON / toString) and
530
+ * warn, rather than throwing. Scalars pass through unchanged.
531
+ */
532
+ private normalizeNonStringSetValue;
498
533
  private getValue;
499
534
  private setValue;
500
535
  get isDirty(): boolean;
@@ -629,6 +664,27 @@ declare class BaseModel implements StringSetChangeTracker {
629
664
  */
630
665
  static count(this: new (...args: any[]) => any, filter?: DocumentFilter, options?: Pick<QueryOptions, "documents">): Promise<number>;
631
666
  static findAll<T extends BaseModel>(this: new (...args: any[]) => T): Promise<T[]>;
667
+ /**
668
+ * Resolve a record by its unique-constraint field values via an engine query.
669
+ * Used as a fallback when the `_uniqueIdx_<model>_<constraint>` map has no
670
+ * entry for a key.
671
+ *
672
+ * The index map is a cache over the SQLite projection, not the source of
673
+ * truth: every record is loaded into the projection on `initializeForDocument`
674
+ * regardless of index presence, but a record written by a client predating
675
+ * index maintenance (legacy data) is missing from the index. Such a record
676
+ * silently escapes both `upsertByUnique` lookups and the `save()` uniqueness
677
+ * check. A query over the constraint fields finds it reliably.
678
+ *
679
+ * Contract: RESOLVE ONLY. Returns the owning record (with `_metaDocId` set to
680
+ * the document that holds it, via `queryOne`) or `null`. Callers decide
681
+ * whether to heal the missing index entry (write paths) and do their own
682
+ * logging — this method never mutates the index, so read paths stay
683
+ * read-only. Mirrors `buildUniqueKey`: a null/undefined value in any
684
+ * constraint field means the record does not participate in the constraint,
685
+ * so no query runs.
686
+ */
687
+ private static _resolveByUniqueFallback;
632
688
  static findByUnique<T extends BaseModel>(this: typeof BaseModel & (new (...args: any[]) => T), constraintName: string, value: any | any[]): Promise<T | null>;
633
689
  static upsertByUnique<T extends BaseModel>(this: typeof BaseModel & (new (...args: any[]) => T), constraintName: string, uniqueLookupValue: any | any[], dataToUpsert: Partial<Omit<InstanceType<new (...args: any[]) => T>, keyof BaseModel | "toJSON">> & {
634
690
  id?: string;
@@ -922,6 +978,7 @@ interface DiscoveredField {
922
978
  required?: boolean;
923
979
  default?: string | number | boolean;
924
980
  autoAssign?: boolean;
981
+ autoStamp?: "create" | "update" | "both";
925
982
  maxLength?: number;
926
983
  maxCount?: number;
927
984
  }
@@ -1010,6 +1067,11 @@ declare function loadSchemaFromTomlString(tomlString: string, options?: LoadSche
1010
1067
 
1011
1068
  /**
1012
1069
  * Infer a _meta_ type string from a JS runtime value.
1070
+ *
1071
+ * Only an all-`true` Y.Map (the stringset wire shape) is tagged as
1072
+ * `stringset` (issue #625) — a Y.Map carrying a composite payload, or a
1073
+ * Y.Array / Y.Text, is not a stringset and stays untyped (`null`) rather
1074
+ * than being mis-tagged.
1013
1075
  */
1014
1076
  declare function inferFieldType(value: unknown): "string" | "number" | "boolean" | "stringset" | null;
1015
1077
  /**
package/dist/browser.d.ts CHANGED
@@ -31,8 +31,32 @@ interface FieldOptions {
31
31
  unique?: boolean;
32
32
  default?: any | (() => any);
33
33
  autoAssign?: boolean;
34
+ /**
35
+ * Auto-timestamp this field with `Date.now()` (milliseconds) on save.
36
+ *
37
+ * - `'create'` — stamp only on the first save (when `isNew === true`).
38
+ * - `'update'` — stamp on every save (create AND update).
39
+ * - `'both'` — stamp on every save (create AND update).
40
+ *
41
+ * If the caller passes an explicit value for the field in `data`, the
42
+ * explicit value wins and the stamp is skipped. The stamp is applied
43
+ * in `beforeSave` BEFORE any user-defined hooks, so user hooks may
44
+ * still overwrite the value if they wish (last writer wins).
45
+ */
46
+ autoStamp?: "create" | "update" | "both";
34
47
  maxLength?: number;
35
48
  maxCount?: number;
49
+ /**
50
+ * Allowed-value set for a `string` field. When present, the codegen
51
+ * generators (database-type codegen + doc-model v2 codegen) emit a
52
+ * TypeScript string-literal union (`"a" | "b" | "c"`) instead of a bare
53
+ * `string` for this field.
54
+ *
55
+ * Advisory / codegen-only: this is a TS-emission hint. The runtime and the
56
+ * server do NOT enforce enum membership on write (see #843). Only valid on
57
+ * `string` fields, and must be a non-empty array of strings.
58
+ */
59
+ enum?: string[];
36
60
  }
37
61
  interface UniqueConstraintConfig {
38
62
  name: string;
@@ -495,6 +519,17 @@ declare class BaseModel implements StringSetChangeTracker {
495
519
  private ensureLocalChanges;
496
520
  private hasLocalChange;
497
521
  private getFromYjs;
522
+ /**
523
+ * Defensive normalization for the read/sync boundary (issue #625).
524
+ *
525
+ * Stringset fields are owned by getStringSetFromYjs (which expects the
526
+ * raw Y.Map / legacy plain-object shape — #561 / #601), so they pass
527
+ * through untouched. For any other field, a composite Yjs primitive
528
+ * (Y.Map / Y.Array / Y.Text) must never reach user code or the DB layer
529
+ * raw: normalize it to its plain-JS projection (toJSON / toString) and
530
+ * warn, rather than throwing. Scalars pass through unchanged.
531
+ */
532
+ private normalizeNonStringSetValue;
498
533
  private getValue;
499
534
  private setValue;
500
535
  get isDirty(): boolean;
@@ -629,6 +664,27 @@ declare class BaseModel implements StringSetChangeTracker {
629
664
  */
630
665
  static count(this: new (...args: any[]) => any, filter?: DocumentFilter, options?: Pick<QueryOptions, "documents">): Promise<number>;
631
666
  static findAll<T extends BaseModel>(this: new (...args: any[]) => T): Promise<T[]>;
667
+ /**
668
+ * Resolve a record by its unique-constraint field values via an engine query.
669
+ * Used as a fallback when the `_uniqueIdx_<model>_<constraint>` map has no
670
+ * entry for a key.
671
+ *
672
+ * The index map is a cache over the SQLite projection, not the source of
673
+ * truth: every record is loaded into the projection on `initializeForDocument`
674
+ * regardless of index presence, but a record written by a client predating
675
+ * index maintenance (legacy data) is missing from the index. Such a record
676
+ * silently escapes both `upsertByUnique` lookups and the `save()` uniqueness
677
+ * check. A query over the constraint fields finds it reliably.
678
+ *
679
+ * Contract: RESOLVE ONLY. Returns the owning record (with `_metaDocId` set to
680
+ * the document that holds it, via `queryOne`) or `null`. Callers decide
681
+ * whether to heal the missing index entry (write paths) and do their own
682
+ * logging — this method never mutates the index, so read paths stay
683
+ * read-only. Mirrors `buildUniqueKey`: a null/undefined value in any
684
+ * constraint field means the record does not participate in the constraint,
685
+ * so no query runs.
686
+ */
687
+ private static _resolveByUniqueFallback;
632
688
  static findByUnique<T extends BaseModel>(this: typeof BaseModel & (new (...args: any[]) => T), constraintName: string, value: any | any[]): Promise<T | null>;
633
689
  static upsertByUnique<T extends BaseModel>(this: typeof BaseModel & (new (...args: any[]) => T), constraintName: string, uniqueLookupValue: any | any[], dataToUpsert: Partial<Omit<InstanceType<new (...args: any[]) => T>, keyof BaseModel | "toJSON">> & {
634
690
  id?: string;
@@ -922,6 +978,7 @@ interface DiscoveredField {
922
978
  required?: boolean;
923
979
  default?: string | number | boolean;
924
980
  autoAssign?: boolean;
981
+ autoStamp?: "create" | "update" | "both";
925
982
  maxLength?: number;
926
983
  maxCount?: number;
927
984
  }
@@ -1010,6 +1067,11 @@ declare function loadSchemaFromTomlString(tomlString: string, options?: LoadSche
1010
1067
 
1011
1068
  /**
1012
1069
  * Infer a _meta_ type string from a JS runtime value.
1070
+ *
1071
+ * Only an all-`true` Y.Map (the stringset wire shape) is tagged as
1072
+ * `stringset` (issue #625) — a Y.Map carrying a composite payload, or a
1073
+ * Y.Array / Y.Text, is not a stringset and stays untyped (`null`) rather
1074
+ * than being mis-tagged.
1013
1075
  */
1014
1076
  declare function inferFieldType(value: unknown): "string" | "number" | "boolean" | "stringset" | null;
1015
1077
  /**