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.
- package/README.md +124 -30
- package/dist/browser.cjs +759 -273
- package/dist/browser.d.cts +62 -0
- package/dist/browser.d.ts +62 -0
- package/dist/browser.js +760 -271
- package/dist/client.cjs +53 -6
- package/dist/client.d.cts +149 -0
- package/dist/client.d.ts +149 -0
- package/dist/client.js +49 -4
- package/dist/cloudflare-do.cjs +643 -231
- package/dist/cloudflare-do.d.cts +237 -1
- package/dist/cloudflare-do.d.ts +237 -1
- package/dist/cloudflare-do.js +639 -229
- package/dist/cloudflare.cjs +330 -26
- package/dist/cloudflare.d.cts +360 -1
- package/dist/cloudflare.d.ts +360 -1
- package/dist/cloudflare.js +326 -24
- package/dist/codegen-v2.cjs +139 -21
- package/dist/codegen.cjs +7 -1
- package/dist/index.cjs +724 -232
- package/dist/index.d.cts +98 -1
- package/dist/index.d.ts +98 -1
- package/dist/index.js +721 -229
- package/dist/node.cjs +948 -299
- package/dist/node.d.cts +220 -1
- package/dist/node.d.ts +220 -1
- package/dist/node.js +934 -296
- package/package.json +2 -1
package/dist/cloudflare.d.cts
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;
|
|
@@ -394,6 +418,17 @@ declare class BaseModel implements StringSetChangeTracker {
|
|
|
394
418
|
private ensureLocalChanges;
|
|
395
419
|
private hasLocalChange;
|
|
396
420
|
private getFromYjs;
|
|
421
|
+
/**
|
|
422
|
+
* Defensive normalization for the read/sync boundary (issue #625).
|
|
423
|
+
*
|
|
424
|
+
* Stringset fields are owned by getStringSetFromYjs (which expects the
|
|
425
|
+
* raw Y.Map / legacy plain-object shape — #561 / #601), so they pass
|
|
426
|
+
* through untouched. For any other field, a composite Yjs primitive
|
|
427
|
+
* (Y.Map / Y.Array / Y.Text) must never reach user code or the DB layer
|
|
428
|
+
* raw: normalize it to its plain-JS projection (toJSON / toString) and
|
|
429
|
+
* warn, rather than throwing. Scalars pass through unchanged.
|
|
430
|
+
*/
|
|
431
|
+
private normalizeNonStringSetValue;
|
|
397
432
|
private getValue;
|
|
398
433
|
private setValue;
|
|
399
434
|
get isDirty(): boolean;
|
|
@@ -528,6 +563,27 @@ declare class BaseModel implements StringSetChangeTracker {
|
|
|
528
563
|
*/
|
|
529
564
|
static count(this: new (...args: any[]) => any, filter?: DocumentFilter, options?: Pick<QueryOptions, "documents">): Promise<number>;
|
|
530
565
|
static findAll<T extends BaseModel>(this: new (...args: any[]) => T): Promise<T[]>;
|
|
566
|
+
/**
|
|
567
|
+
* Resolve a record by its unique-constraint field values via an engine query.
|
|
568
|
+
* Used as a fallback when the `_uniqueIdx_<model>_<constraint>` map has no
|
|
569
|
+
* entry for a key.
|
|
570
|
+
*
|
|
571
|
+
* The index map is a cache over the SQLite projection, not the source of
|
|
572
|
+
* truth: every record is loaded into the projection on `initializeForDocument`
|
|
573
|
+
* regardless of index presence, but a record written by a client predating
|
|
574
|
+
* index maintenance (legacy data) is missing from the index. Such a record
|
|
575
|
+
* silently escapes both `upsertByUnique` lookups and the `save()` uniqueness
|
|
576
|
+
* check. A query over the constraint fields finds it reliably.
|
|
577
|
+
*
|
|
578
|
+
* Contract: RESOLVE ONLY. Returns the owning record (with `_metaDocId` set to
|
|
579
|
+
* the document that holds it, via `queryOne`) or `null`. Callers decide
|
|
580
|
+
* whether to heal the missing index entry (write paths) and do their own
|
|
581
|
+
* logging — this method never mutates the index, so read paths stay
|
|
582
|
+
* read-only. Mirrors `buildUniqueKey`: a null/undefined value in any
|
|
583
|
+
* constraint field means the record does not participate in the constraint,
|
|
584
|
+
* so no query runs.
|
|
585
|
+
*/
|
|
586
|
+
private static _resolveByUniqueFallback;
|
|
531
587
|
static findByUnique<T extends BaseModel>(this: typeof BaseModel & (new (...args: any[]) => T), constraintName: string, value: any | any[]): Promise<T | null>;
|
|
532
588
|
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">> & {
|
|
533
589
|
id?: string;
|
|
@@ -698,6 +754,18 @@ interface HookResult {
|
|
|
698
754
|
allow: boolean;
|
|
699
755
|
/** Reason for denial (returned to client as error message) */
|
|
700
756
|
reason?: string;
|
|
757
|
+
/**
|
|
758
|
+
* Names of fields the hook wrote onto `ctx.data` that the DO should echo
|
|
759
|
+
* back to the caller in the write response's `appliedFields`. A `beforeSave`
|
|
760
|
+
* hook can compute and set fields on `ctx.data` (e.g. app-level triggers that
|
|
761
|
+
* derive a value from the submitted row); those values are persisted but are
|
|
762
|
+
* invisible to a caller that rebuilds a change event from its original
|
|
763
|
+
* submission inputs. Reporting the names here lets the DO read the final
|
|
764
|
+
* post-hook values back and thread them into `appliedFields` so the caller
|
|
765
|
+
* can propagate them (#1476). The hook contract stays generic — it names the
|
|
766
|
+
* fields it applied and does not need to know how the caller uses them.
|
|
767
|
+
*/
|
|
768
|
+
serverAppliedFieldNames?: string[];
|
|
701
769
|
}
|
|
702
770
|
/** Result from beforeQuery hooks — can inject additional filters */
|
|
703
771
|
interface BeforeQueryResult extends HookResult {
|
|
@@ -754,6 +822,25 @@ interface SaveRequest {
|
|
|
754
822
|
ifNotExists?: boolean;
|
|
755
823
|
condition?: DocumentFilter;
|
|
756
824
|
upsertOn?: string;
|
|
825
|
+
/**
|
|
826
|
+
* Per-call auto-timestamp directives for schemaless writes.
|
|
827
|
+
*
|
|
828
|
+
* Each entry is a list of field names to stamp with `Date.now()`
|
|
829
|
+
* (milliseconds) at the corresponding lifecycle event:
|
|
830
|
+
*
|
|
831
|
+
* - `create` — field is stamped only when the record is new
|
|
832
|
+
* (no row exists yet for this `modelName` + `id`).
|
|
833
|
+
* - `update` — field is stamped on every save (create AND update).
|
|
834
|
+
*
|
|
835
|
+
* If the caller already provided an explicit value for the field
|
|
836
|
+
* in `data`, the explicit value wins and the stamp is skipped.
|
|
837
|
+
* Stamping happens BEFORE the `beforeSave` user hook runs, so user
|
|
838
|
+
* hooks may still overwrite the stamped value if they wish.
|
|
839
|
+
*/
|
|
840
|
+
autoStamps?: {
|
|
841
|
+
create?: string[];
|
|
842
|
+
update?: string[];
|
|
843
|
+
};
|
|
757
844
|
}
|
|
758
845
|
interface DeleteRequest {
|
|
759
846
|
modelName: string;
|
|
@@ -795,6 +882,20 @@ interface QueryResponse {
|
|
|
795
882
|
interface SaveResponse {
|
|
796
883
|
success: boolean;
|
|
797
884
|
id: string;
|
|
885
|
+
/**
|
|
886
|
+
* Server-applied field values the DO wrote AFTER the caller's submission
|
|
887
|
+
* inputs were assembled, read back from the final post-hook `data`
|
|
888
|
+
* (#1476, consolidating the #1462 `stamps` channel). This is the single
|
|
889
|
+
* "server-applied fields" channel: it carries both the applied `autoStamps`
|
|
890
|
+
* timestamps and any fields a `beforeSave` hook reported via
|
|
891
|
+
* `serverAppliedFieldNames` (e.g. app-level trigger-computed values). Present
|
|
892
|
+
* only when at least one such field ended up with a value. Lets the Worker
|
|
893
|
+
* thread these values into the change-event `data` it broadcasts to
|
|
894
|
+
* subscribers — otherwise a `select`-listed stamped or trigger-computed field
|
|
895
|
+
* never reaches the frame, because the frame is rebuilt from the submission
|
|
896
|
+
* inputs (which predate the DO-side write).
|
|
897
|
+
*/
|
|
898
|
+
appliedFields?: Record<string, any>;
|
|
798
899
|
}
|
|
799
900
|
interface DeleteResponse {
|
|
800
901
|
success: boolean;
|
|
@@ -862,10 +963,23 @@ interface PatchRequest {
|
|
|
862
963
|
data: Record<string, any>;
|
|
863
964
|
stringSets?: Record<string, string[]>;
|
|
864
965
|
condition?: DocumentFilter;
|
|
966
|
+
/**
|
|
967
|
+
* Per-call auto-timestamp directives. A patch is always treated as
|
|
968
|
+
* an update (`isNew === false`), so only the `update` list is
|
|
969
|
+
* applied; the `create` list is ignored on patch.
|
|
970
|
+
*
|
|
971
|
+
* Explicit values in `data` win.
|
|
972
|
+
*/
|
|
973
|
+
autoStamps?: {
|
|
974
|
+
create?: string[];
|
|
975
|
+
update?: string[];
|
|
976
|
+
};
|
|
865
977
|
}
|
|
866
978
|
interface PatchResponse {
|
|
867
979
|
success: boolean;
|
|
868
980
|
id: string;
|
|
981
|
+
/** Server-applied field values (#1476). See `SaveResponse.appliedFields`. */
|
|
982
|
+
appliedFields?: Record<string, any>;
|
|
869
983
|
}
|
|
870
984
|
interface BatchOperation {
|
|
871
985
|
op: "save" | "patch" | "delete" | "increment" | "addToSet" | "removeFromSet";
|
|
@@ -877,15 +991,62 @@ interface BatchOperation {
|
|
|
877
991
|
ifNotExists?: boolean;
|
|
878
992
|
condition?: DocumentFilter;
|
|
879
993
|
upsertOn?: string;
|
|
994
|
+
/**
|
|
995
|
+
* Per-operation auto-timestamp directives. Same semantics as
|
|
996
|
+
* `SaveRequest.autoStamps` / `PatchRequest.autoStamps`. Applied
|
|
997
|
+
* BEFORE the user `beforeSave` hook runs, with explicit values
|
|
998
|
+
* in `data` always winning.
|
|
999
|
+
*
|
|
1000
|
+
* For `op: "patch"`, only the `update` list applies (patches are
|
|
1001
|
+
* always treated as updates).
|
|
1002
|
+
*/
|
|
1003
|
+
autoStamps?: {
|
|
1004
|
+
create?: string[];
|
|
1005
|
+
update?: string[];
|
|
1006
|
+
};
|
|
880
1007
|
}
|
|
881
1008
|
interface BatchRequest {
|
|
882
1009
|
operations: BatchOperation[];
|
|
1010
|
+
/**
|
|
1011
|
+
* All-or-nothing execution (#1478). When `true`, the FIRST operation that
|
|
1012
|
+
* fails inside the transaction — hook denial, condition / `ifNotExists` /
|
|
1013
|
+
* unique violation, record-not-found, or upsert validation error — aborts
|
|
1014
|
+
* the whole batch: every write is rolled back and the handler returns an
|
|
1015
|
+
* error naming the failed step with its natural HTTP status (see
|
|
1016
|
+
* `BatchOperationResult.status`). When `false` or absent (the default), each
|
|
1017
|
+
* operation's outcome is recorded independently and successful siblings
|
|
1018
|
+
* commit — the partial-success behavior import-bulk and applyToQuery rely on.
|
|
1019
|
+
*/
|
|
1020
|
+
atomic?: boolean;
|
|
883
1021
|
}
|
|
1022
|
+
/**
|
|
1023
|
+
* Stable, machine-readable failure tag on a failed `BatchOperationResult`
|
|
1024
|
+
* (#1478). Derived at each failure site — never inferred from the `error`
|
|
1025
|
+
* string. `#1389` (conditional writes + fencing-token enforcement) surfaces its
|
|
1026
|
+
* condition / stale-token rejections through this same channel.
|
|
1027
|
+
*/
|
|
1028
|
+
type BatchFailureCode = "NOT_FOUND" | "HOOK_DENIED" | "CONDITION_NOT_MET" | "ALREADY_EXISTS" | "UNIQUE_VIOLATION" | "VALIDATION_ERROR";
|
|
884
1029
|
interface BatchOperationResult {
|
|
885
1030
|
success: boolean;
|
|
886
1031
|
id: string;
|
|
887
1032
|
error?: string;
|
|
1033
|
+
/**
|
|
1034
|
+
* Structured failure metadata (#1478), present only on a failed result. Both
|
|
1035
|
+
* are derived at the failure site, not parsed back out of `error`:
|
|
1036
|
+
* - `status` — the natural HTTP status for the cause (404 not found,
|
|
1037
|
+
* 403 hook denial, 409 condition / `ifNotExists` / unique conflict,
|
|
1038
|
+
* 400 upsert validation).
|
|
1039
|
+
* - `code` — a stable machine-readable tag (see `BatchFailureCode`).
|
|
1040
|
+
*
|
|
1041
|
+
* In atomic mode the handler reads the first failure's `status`/`code` to
|
|
1042
|
+
* build the error response, so an increment-on-missing op keeps its 404 and a
|
|
1043
|
+
* hook denial keeps its 403 instead of collapsing to a uniform 409.
|
|
1044
|
+
*/
|
|
1045
|
+
status?: number;
|
|
1046
|
+
code?: BatchFailureCode;
|
|
888
1047
|
values?: Record<string, number>;
|
|
1048
|
+
/** Server-applied field values (#1476). See `SaveResponse.appliedFields`. */
|
|
1049
|
+
appliedFields?: Record<string, any>;
|
|
889
1050
|
}
|
|
890
1051
|
interface BatchResponse {
|
|
891
1052
|
results: BatchOperationResult[];
|
|
@@ -1435,6 +1596,7 @@ interface DiscoveredField {
|
|
|
1435
1596
|
required?: boolean;
|
|
1436
1597
|
default?: string | number | boolean;
|
|
1437
1598
|
autoAssign?: boolean;
|
|
1599
|
+
autoStamp?: "create" | "update" | "both";
|
|
1438
1600
|
maxLength?: number;
|
|
1439
1601
|
maxCount?: number;
|
|
1440
1602
|
}
|
|
@@ -1525,6 +1687,41 @@ interface LoadSchemaOptions {
|
|
|
1525
1687
|
*/
|
|
1526
1688
|
declare function loadSchemaFromTomlString(tomlString: string, options?: LoadSchemaOptions): DefinedModelSchema[];
|
|
1527
1689
|
|
|
1690
|
+
/**
|
|
1691
|
+
* Unique-constraint index helpers (shared CRDT contract).
|
|
1692
|
+
*
|
|
1693
|
+
* js-bao enforces unique constraints inside the Yjs document, not via a SQL
|
|
1694
|
+
* `UNIQUE`. Each constraint has a `_uniqueIdx_<model>_<constraint>` map
|
|
1695
|
+
* (uniqueKey -> surviving recordId) and a document observer that
|
|
1696
|
+
* deterministically discards duplicate losers. Both the client write path and
|
|
1697
|
+
* the server-direct write path (`mutateRecord` in js-bao-wss) must derive the
|
|
1698
|
+
* key and the index map name the SAME way, or a server-written record would
|
|
1699
|
+
* not be seen by the client-side resolver. These helpers are the single source
|
|
1700
|
+
* of truth for that derivation so neither side keeps a parallel copy.
|
|
1701
|
+
*/
|
|
1702
|
+
|
|
1703
|
+
/**
|
|
1704
|
+
* Build the unique-constraint key for a record over the given fields.
|
|
1705
|
+
*
|
|
1706
|
+
* Mirrors the document-observer conflict resolver: a `null`/`undefined` value
|
|
1707
|
+
* in any field means the record does not participate in the constraint (returns
|
|
1708
|
+
* `null`); otherwise the per-field `String()` values joined by `"|"`. This is
|
|
1709
|
+
* the key the `_uniqueIdx_*` map is read/written with during conflict
|
|
1710
|
+
* resolution, so it is the key the resolver treats as authoritative.
|
|
1711
|
+
*/
|
|
1712
|
+
declare function buildUniqueKey(recordData: Y.Map<any> | Record<string, any>, fields: string[]): string | null;
|
|
1713
|
+
/**
|
|
1714
|
+
* Name of the Yjs map that indexes a unique constraint within a document:
|
|
1715
|
+
* `_uniqueIdx_<model>_<constraint>` mapping uniqueKey -> surviving recordId.
|
|
1716
|
+
*/
|
|
1717
|
+
declare function uniqueIndexMapName(modelName: string, constraintName: string): string;
|
|
1718
|
+
/**
|
|
1719
|
+
* The constraint name js-bao assigns to a single-field `unique: true` field
|
|
1720
|
+
* (see `resolveUniqueConstraints` in models/schema.ts). Compound constraints
|
|
1721
|
+
* keep their author-given name instead.
|
|
1722
|
+
*/
|
|
1723
|
+
declare function fieldUniqueConstraintName(modelName: string, fieldName: string): string;
|
|
1724
|
+
|
|
1528
1725
|
/**
|
|
1529
1726
|
* Meta Sync — writes _meta_* YMaps into a YDoc.
|
|
1530
1727
|
*
|
|
@@ -1541,6 +1738,11 @@ declare function loadSchemaFromTomlString(tomlString: string, options?: LoadSche
|
|
|
1541
1738
|
|
|
1542
1739
|
/**
|
|
1543
1740
|
* Infer a _meta_ type string from a JS runtime value.
|
|
1741
|
+
*
|
|
1742
|
+
* Only an all-`true` Y.Map (the stringset wire shape) is tagged as
|
|
1743
|
+
* `stringset` (issue #625) — a Y.Map carrying a composite payload, or a
|
|
1744
|
+
* Y.Array / Y.Text, is not a stringset and stays untyped (`null`) rather
|
|
1745
|
+
* than being mis-tagged.
|
|
1544
1746
|
*/
|
|
1545
1747
|
declare function inferFieldType(value: unknown): "string" | "number" | "boolean" | "stringset" | null;
|
|
1546
1748
|
/**
|
|
@@ -1572,4 +1774,161 @@ declare function syncModelMeta(yDoc: Y.Doc, modelName: string, schema: ModelSche
|
|
|
1572
1774
|
*/
|
|
1573
1775
|
declare function syncInferredMeta(yDoc: Y.Doc, modelName: string, recordData: Record<string, any>): void;
|
|
1574
1776
|
|
|
1575
|
-
|
|
1777
|
+
/**
|
|
1778
|
+
* Write lifecycle — shared, schema-keyed write behavior for both the js-bao
|
|
1779
|
+
* client (`BaseModel.save()`) and the server-direct workflow write path
|
|
1780
|
+
* (`mutateRecord` in js-bao-wss `src/yjs-room-v2.ts`).
|
|
1781
|
+
*
|
|
1782
|
+
* Background (#1246): the server re-implemented js-bao's client write behavior,
|
|
1783
|
+
* so the two paths could drift and new js-bao field types were not picked up by
|
|
1784
|
+
* workflow writes automatically. This module is the single source of truth for
|
|
1785
|
+
* the *pure transform* (data + schema → Yjs structure), keyed only off schema
|
|
1786
|
+
* metadata — no ORM concerns (reactivity, local-change tracking, document
|
|
1787
|
+
* connection). Both paths call these helpers, so:
|
|
1788
|
+
*
|
|
1789
|
+
* 1. the stringset Yjs-shape primitives are written the same way on both sides;
|
|
1790
|
+
* 2. schema `default`s and `autoStamp`s are applied the same way on both sides;
|
|
1791
|
+
* 3. a new `FieldType` breaks `fieldWriteKind`'s exhaustive switch until it is
|
|
1792
|
+
* classified once — and the server, calling the same core, inherits it.
|
|
1793
|
+
*
|
|
1794
|
+
* The unique-index contract already lives in `../utils/uniqueIndex.ts`; this
|
|
1795
|
+
* module is the same "neither side keeps a parallel copy" pattern generalized
|
|
1796
|
+
* to the rest of the write lifecycle.
|
|
1797
|
+
*/
|
|
1798
|
+
|
|
1799
|
+
/**
|
|
1800
|
+
* Compile-time exhaustiveness guard. Passing a value whose type is not `never`
|
|
1801
|
+
* is a TypeScript error, so an unhandled `case` in an exhaustive `switch` fails
|
|
1802
|
+
* `tsc`. At runtime (reached only for a genuinely unknown value) it throws.
|
|
1803
|
+
*/
|
|
1804
|
+
declare function assertNever(x: never): never;
|
|
1805
|
+
/**
|
|
1806
|
+
* How a field's declared type is written into the record Y.Map. Every member
|
|
1807
|
+
* of the `FieldType` union must be classified here; adding a new field type to
|
|
1808
|
+
* the union without a `case` below fails `pnpm typecheck` (via `assertNever`),
|
|
1809
|
+
* which is what forces both write paths to handle it. (#1246, layer 2.)
|
|
1810
|
+
*/
|
|
1811
|
+
type FieldWriteKind = "scalar" | "stringset";
|
|
1812
|
+
declare function fieldWriteKind(type: FieldType): FieldWriteKind;
|
|
1813
|
+
/** True if `type` is a member of the `FieldType` union. */
|
|
1814
|
+
declare function isKnownFieldType(type: string): type is FieldType;
|
|
1815
|
+
/**
|
|
1816
|
+
* Resolve (or migrate to) the canonical nested `Y.Map<boolean>` for a
|
|
1817
|
+
* `stringset` field. A legacy plain-object value is carried over member by
|
|
1818
|
+
* member; the nested map is never destroyed and rebuilt, so a concurrent
|
|
1819
|
+
* client `.add()`/`.delete()` merges instead of being clobbered.
|
|
1820
|
+
*/
|
|
1821
|
+
declare function ensureStringSetYMap(rm: Y.Map<any>, fieldName: string): Y.Map<boolean>;
|
|
1822
|
+
interface StringSetDelta {
|
|
1823
|
+
add?: Iterable<string>;
|
|
1824
|
+
remove?: Iterable<string>;
|
|
1825
|
+
clear?: boolean;
|
|
1826
|
+
}
|
|
1827
|
+
/**
|
|
1828
|
+
* Apply a per-member DELTA to a stringset target map. `clear` empties the set
|
|
1829
|
+
* first; then `remove` deletes, then `add` sets — so an `add` wins over a
|
|
1830
|
+
* `remove` of the same member within one delta. Members not named are left
|
|
1831
|
+
* untouched, so two concurrent `add`s of different members merge cleanly.
|
|
1832
|
+
*/
|
|
1833
|
+
declare function applyStringSetDelta(target: Y.Map<boolean>, delta: StringSetDelta): void;
|
|
1834
|
+
/**
|
|
1835
|
+
* REPLACE a stringset field with exactly `members`, applied as a per-member
|
|
1836
|
+
* CRDT diff (set members present, delete members no longer present) so a
|
|
1837
|
+
* concurrent client edit merges instead of being clobbered by a
|
|
1838
|
+
* destroy-and-rebuild.
|
|
1839
|
+
*/
|
|
1840
|
+
declare function replaceStringSet(rm: Y.Map<any>, fieldName: string, members: Iterable<unknown>): void;
|
|
1841
|
+
/**
|
|
1842
|
+
* Apply the client save-diff stringset change shape (`{additions, removals}`)
|
|
1843
|
+
* to the parent record's Y.Map. Thin wrapper over `applyStringSetDelta` so the
|
|
1844
|
+
* js-bao client and the server write a stringset the exact same way.
|
|
1845
|
+
*/
|
|
1846
|
+
declare function applyStringSetChange(recordYMap: Y.Map<any>, fieldName: string, change: {
|
|
1847
|
+
additions: Set<string>;
|
|
1848
|
+
removals: Set<string>;
|
|
1849
|
+
}): void;
|
|
1850
|
+
interface ParsedStringSetOp {
|
|
1851
|
+
add: string[];
|
|
1852
|
+
remove: string[];
|
|
1853
|
+
clear: boolean;
|
|
1854
|
+
}
|
|
1855
|
+
/**
|
|
1856
|
+
* Recognize a `stringset` DELTA op-object inside a write payload:
|
|
1857
|
+
* `{ $add?: string[], $remove?: string[], $clear?: true }`. Returns the
|
|
1858
|
+
* normalized op, or `null` when `value` is not an op-object (a plain array
|
|
1859
|
+
* keeps its REPLACE meaning; a scalar is a scalar). Throws on a malformed op
|
|
1860
|
+
* (unknown `$key`, or `$add`/`$remove` not an array) so an author typo
|
|
1861
|
+
* surfaces instead of silently writing nothing. (#1242, moved here in #1246.)
|
|
1862
|
+
*/
|
|
1863
|
+
declare function parseStringSetOp(value: unknown): ParsedStringSetOp | null;
|
|
1864
|
+
/**
|
|
1865
|
+
* Write a single field's value into the record Y.Map according to the field's
|
|
1866
|
+
* declared type. Scalars are `.set()` directly; a `stringset` takes an array
|
|
1867
|
+
* (REPLACE) or a `{ $add/$remove/$clear }` op-object (DELTA). A collection
|
|
1868
|
+
* value in a non-stringset slot, or a scalar in a stringset slot, is rejected
|
|
1869
|
+
* (mirrors #1242 guardrails) rather than silently corrupting the record.
|
|
1870
|
+
*
|
|
1871
|
+
* `declaredType` is the resolved `_meta_` type (may be `undefined` when the doc
|
|
1872
|
+
* has no `_meta_` for the model — first-writer / legacy). An unresolved or
|
|
1873
|
+
* unknown type is treated as scalar for backward compatibility, but a
|
|
1874
|
+
* collection value still requires a real `stringset` type.
|
|
1875
|
+
*/
|
|
1876
|
+
declare function writeField(rm: Y.Map<any>, declaredType: string | undefined, key: string, value: unknown, modelName: string): void;
|
|
1877
|
+
declare function registerFunctionDefaultGenerator(name: string, fn: () => any): void;
|
|
1878
|
+
declare function decodeFunctionDefault(name: string): (() => any) | undefined;
|
|
1879
|
+
interface SchemaFieldLike {
|
|
1880
|
+
type?: string;
|
|
1881
|
+
default?: any;
|
|
1882
|
+
autoStamp?: "create" | "update" | "both";
|
|
1883
|
+
required?: boolean;
|
|
1884
|
+
unique?: boolean;
|
|
1885
|
+
}
|
|
1886
|
+
/**
|
|
1887
|
+
* Resolve a field's declared default to a concrete value:
|
|
1888
|
+
* - a function → call it (client path holds real function refs);
|
|
1889
|
+
* - a `"$$literal"` string → an escaped literal; unescape the leading `$$` to
|
|
1890
|
+
* one `$` and use it verbatim (a literal default like `default: "$0.00"`,
|
|
1891
|
+
* which metaSync doubled on encode so it is not mistaken for a function ref);
|
|
1892
|
+
* - a `"$name"` string → decode via the registry and call it (server path,
|
|
1893
|
+
* where `_meta_` `$`-encodes function defaults);
|
|
1894
|
+
* - anything else → the literal value.
|
|
1895
|
+
*
|
|
1896
|
+
* An unknown `"$name"` throws (fail loud) rather than silently leaving the
|
|
1897
|
+
* field unset or writing the literal `"$..."` string into the record — a
|
|
1898
|
+
* missing server-populated field must not hide (#1246, Fork 3).
|
|
1899
|
+
*/
|
|
1900
|
+
declare function resolveDefaultValue(rawDefault: any, opts?: {
|
|
1901
|
+
fieldName?: string;
|
|
1902
|
+
modelName?: string;
|
|
1903
|
+
decode?: (name: string) => (() => any) | undefined;
|
|
1904
|
+
}): any;
|
|
1905
|
+
/**
|
|
1906
|
+
* Apply schema `default`s to a plain data object for a CREATE. A field with a
|
|
1907
|
+
* declared default that the caller did not set (absent or `undefined`) is
|
|
1908
|
+
* populated; an explicit caller value always wins. `id` is skipped — both
|
|
1909
|
+
* paths assign the id separately. Pure: mutates and returns `data`.
|
|
1910
|
+
*/
|
|
1911
|
+
declare function applyDefaultsFromSchema(fields: Record<string, SchemaFieldLike>, data: Record<string, any>, opts?: {
|
|
1912
|
+
modelName?: string;
|
|
1913
|
+
decodeFunctionDefault?: (name: string) => (() => any) | undefined;
|
|
1914
|
+
}): Record<string, any>;
|
|
1915
|
+
/**
|
|
1916
|
+
* Apply schema `autoStamp` timestamps to a plain data object. Pure transform,
|
|
1917
|
+
* with `now` injected by the caller (server passes `Date.now()`; tests pass a
|
|
1918
|
+
* fixed clock) so the result is deterministic and testable — no global clock.
|
|
1919
|
+
*
|
|
1920
|
+
* Semantics mirror the client `BaseModel.save()` stamp block:
|
|
1921
|
+
* - `'create'` → stamp only when `isNew` and the caller did not set the field
|
|
1922
|
+
* explicitly (a non-null value present in `data`);
|
|
1923
|
+
* - `'update'` / `'both'` → stamp on every save unless the caller set the
|
|
1924
|
+
* field explicitly.
|
|
1925
|
+
*
|
|
1926
|
+
* An explicit caller value (present and non-null in `data`) always wins.
|
|
1927
|
+
* Mutates and returns `data`.
|
|
1928
|
+
*/
|
|
1929
|
+
declare function applyAutoStampsFromSchema(fields: Record<string, SchemaFieldLike>, data: Record<string, any>, opts: {
|
|
1930
|
+
isNew: boolean;
|
|
1931
|
+
now: number;
|
|
1932
|
+
}): Record<string, any>;
|
|
1933
|
+
|
|
1934
|
+
export { type AfterQueryContext, type AfterQueryResult, type AggregateRequest, type AggregateResponse, type AggregationOperation, type AggregationOptions, type AggregationResult, type BatchOperation, type BatchOperationResult, type BatchRequest, type BatchResponse, type BeforeDeleteContext, type BeforeQueryContext, type BeforeQueryResult, type BeforeSaveContext, type ConnectDoDbOptions, type CountRequest, type CountResponse, DOClientEngine, type DOClientEngineConfig, type DatabaseDOHooks, type DeleteRequest, type DeleteResponse, type DiscoveredConstraint, type DiscoveredField, type DiscoveredModel, type DiscoveredRelationship, type DiscoveredSchema, type DoDb, type DocumentFilter, type DropIndexRequest, type DropIndexResponse, type DropUniqueConstraintRequest, type DropUniqueConstraintResponse, type ErrorResponse, type FieldType, type FieldWriteKind, type GroupByField, type HookContext, type HookResult, type IncrementRequest, type IncrementResponse, type IndexEntry, type IndexListResponse, type InitJsBaoDOOptions, type InitJsBaoDOResult, type ModelAccessor, type PaginatedResult, type ParsedStringSetOp, type PatchOptions, type PatchRequest, type PatchResponse, type ProjectionSpec, type QueryOptions, type QueryRequest, type QueryResponse, type RegisterIndexRequest, type RegisterIndexResponse, type RegisterUniqueConstraintRequest, type RegisterUniqueConstraintResponse, type SaveOptions, type SaveRequest, type SaveResponse, type SchemaFieldLike, type SortSpec, type StringSetDelta, type StringSetMembership, type StringSetUpdateRequest, type StringSetUpdateResponse, type SyncIndexesRequest, type SyncIndexesResponse, type UniqueConstraintEntry, type UniqueConstraintListResponse, type WriteCondition, applyAutoStampsFromSchema, applyDefaultsFromSchema, applyStringSetChange, applyStringSetDelta, assertNever, buildUniqueKey, clearMetaSyncCache, connectDoDb, decodeFunctionDefault, discoverModelNames, discoverSchema, ensureStringSetYMap, fieldUniqueConstraintName, fieldWriteKind, getActiveDOEngine, inferFieldType, initJsBaoDO, isKnownFieldType, loadSchemaFromTomlString, parseStringSetOp, registerFunctionDefault, registerFunctionDefaultGenerator, replaceStringSet, resetJsBaoDO, resolveDefaultValue, schemaToToml, syncInferredMeta, syncModelMeta, uniqueIndexMapName, writeField };
|