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/client.cjs
CHANGED
|
@@ -84,35 +84,82 @@ var init_DocumentQueryTranslator = __esm({
|
|
|
84
84
|
}
|
|
85
85
|
});
|
|
86
86
|
|
|
87
|
+
// src/utils/yjsValues.ts
|
|
88
|
+
var Y;
|
|
89
|
+
var init_yjsValues = __esm({
|
|
90
|
+
"src/utils/yjsValues.ts"() {
|
|
91
|
+
"use strict";
|
|
92
|
+
Y = __toESM(require("yjs"), 1);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// src/models/writeLifecycle.ts
|
|
97
|
+
function registerFunctionDefaultGenerator(name, fn) {
|
|
98
|
+
FUNCTION_DEFAULT_GENERATORS.set(name, fn);
|
|
99
|
+
}
|
|
100
|
+
var Y2, import_ulid, KNOWN_FIELD_TYPE_MAP, KNOWN_FIELD_TYPES, FUNCTION_DEFAULT_GENERATORS;
|
|
101
|
+
var init_writeLifecycle = __esm({
|
|
102
|
+
"src/models/writeLifecycle.ts"() {
|
|
103
|
+
"use strict";
|
|
104
|
+
Y2 = __toESM(require("yjs"), 1);
|
|
105
|
+
import_ulid = require("ulid");
|
|
106
|
+
KNOWN_FIELD_TYPE_MAP = {
|
|
107
|
+
string: true,
|
|
108
|
+
number: true,
|
|
109
|
+
boolean: true,
|
|
110
|
+
date: true,
|
|
111
|
+
id: true,
|
|
112
|
+
stringset: true
|
|
113
|
+
};
|
|
114
|
+
KNOWN_FIELD_TYPES = new Set(Object.keys(KNOWN_FIELD_TYPE_MAP));
|
|
115
|
+
FUNCTION_DEFAULT_GENERATORS = /* @__PURE__ */ new Map();
|
|
116
|
+
registerFunctionDefaultGenerator("generate_ulid", () => (0, import_ulid.ulid)());
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
87
120
|
// src/models/metaSync.ts
|
|
88
121
|
function registerFunctionDefault(fn, name) {
|
|
89
122
|
KNOWN_FUNCTION_DEFAULTS.set(fn, name);
|
|
90
123
|
}
|
|
91
|
-
var
|
|
124
|
+
var Y3, KNOWN_FUNCTION_DEFAULTS;
|
|
92
125
|
var init_metaSync = __esm({
|
|
93
126
|
"src/models/metaSync.ts"() {
|
|
94
127
|
"use strict";
|
|
95
|
-
|
|
128
|
+
Y3 = __toESM(require("yjs"), 1);
|
|
129
|
+
init_yjsValues();
|
|
130
|
+
init_writeLifecycle();
|
|
96
131
|
KNOWN_FUNCTION_DEFAULTS = /* @__PURE__ */ new WeakMap();
|
|
97
132
|
}
|
|
98
133
|
});
|
|
99
134
|
|
|
135
|
+
// src/utils/uniqueIndex.ts
|
|
136
|
+
var Y4;
|
|
137
|
+
var init_uniqueIndex = __esm({
|
|
138
|
+
"src/utils/uniqueIndex.ts"() {
|
|
139
|
+
"use strict";
|
|
140
|
+
Y4 = __toESM(require("yjs"), 1);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
100
144
|
// src/models/BaseModel.ts
|
|
101
145
|
function generateULID() {
|
|
102
|
-
return (0,
|
|
146
|
+
return (0, import_ulid2.ulid)();
|
|
103
147
|
}
|
|
104
|
-
var
|
|
148
|
+
var Y5, import_ulid2, Logger;
|
|
105
149
|
var init_BaseModel = __esm({
|
|
106
150
|
"src/models/BaseModel.ts"() {
|
|
107
151
|
"use strict";
|
|
108
|
-
|
|
109
|
-
|
|
152
|
+
Y5 = __toESM(require("yjs"), 1);
|
|
153
|
+
import_ulid2 = require("ulid");
|
|
110
154
|
init_StringSet();
|
|
111
155
|
init_documentTypes();
|
|
112
156
|
init_DocumentQueryTranslator();
|
|
113
157
|
init_CursorManager();
|
|
114
158
|
init_sql();
|
|
115
159
|
init_metaSync();
|
|
160
|
+
init_writeLifecycle();
|
|
161
|
+
init_yjsValues();
|
|
162
|
+
init_uniqueIndex();
|
|
116
163
|
Logger = class {
|
|
117
164
|
static _logLevel = 1 /* ERROR */;
|
|
118
165
|
static _logCallback = null;
|
package/dist/client.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;
|
|
@@ -682,6 +738,25 @@ interface SaveRequest {
|
|
|
682
738
|
ifNotExists?: boolean;
|
|
683
739
|
condition?: DocumentFilter;
|
|
684
740
|
upsertOn?: string;
|
|
741
|
+
/**
|
|
742
|
+
* Per-call auto-timestamp directives for schemaless writes.
|
|
743
|
+
*
|
|
744
|
+
* Each entry is a list of field names to stamp with `Date.now()`
|
|
745
|
+
* (milliseconds) at the corresponding lifecycle event:
|
|
746
|
+
*
|
|
747
|
+
* - `create` — field is stamped only when the record is new
|
|
748
|
+
* (no row exists yet for this `modelName` + `id`).
|
|
749
|
+
* - `update` — field is stamped on every save (create AND update).
|
|
750
|
+
*
|
|
751
|
+
* If the caller already provided an explicit value for the field
|
|
752
|
+
* in `data`, the explicit value wins and the stamp is skipped.
|
|
753
|
+
* Stamping happens BEFORE the `beforeSave` user hook runs, so user
|
|
754
|
+
* hooks may still overwrite the stamped value if they wish.
|
|
755
|
+
*/
|
|
756
|
+
autoStamps?: {
|
|
757
|
+
create?: string[];
|
|
758
|
+
update?: string[];
|
|
759
|
+
};
|
|
685
760
|
}
|
|
686
761
|
interface DeleteRequest {
|
|
687
762
|
modelName: string;
|
|
@@ -704,6 +779,20 @@ interface QueryResponse {
|
|
|
704
779
|
interface SaveResponse {
|
|
705
780
|
success: boolean;
|
|
706
781
|
id: string;
|
|
782
|
+
/**
|
|
783
|
+
* Server-applied field values the DO wrote AFTER the caller's submission
|
|
784
|
+
* inputs were assembled, read back from the final post-hook `data`
|
|
785
|
+
* (#1476, consolidating the #1462 `stamps` channel). This is the single
|
|
786
|
+
* "server-applied fields" channel: it carries both the applied `autoStamps`
|
|
787
|
+
* timestamps and any fields a `beforeSave` hook reported via
|
|
788
|
+
* `serverAppliedFieldNames` (e.g. app-level trigger-computed values). Present
|
|
789
|
+
* only when at least one such field ended up with a value. Lets the Worker
|
|
790
|
+
* thread these values into the change-event `data` it broadcasts to
|
|
791
|
+
* subscribers — otherwise a `select`-listed stamped or trigger-computed field
|
|
792
|
+
* never reaches the frame, because the frame is rebuilt from the submission
|
|
793
|
+
* inputs (which predate the DO-side write).
|
|
794
|
+
*/
|
|
795
|
+
appliedFields?: Record<string, any>;
|
|
707
796
|
}
|
|
708
797
|
interface DeleteResponse {
|
|
709
798
|
success: boolean;
|
|
@@ -745,10 +834,23 @@ interface PatchRequest {
|
|
|
745
834
|
data: Record<string, any>;
|
|
746
835
|
stringSets?: Record<string, string[]>;
|
|
747
836
|
condition?: DocumentFilter;
|
|
837
|
+
/**
|
|
838
|
+
* Per-call auto-timestamp directives. A patch is always treated as
|
|
839
|
+
* an update (`isNew === false`), so only the `update` list is
|
|
840
|
+
* applied; the `create` list is ignored on patch.
|
|
841
|
+
*
|
|
842
|
+
* Explicit values in `data` win.
|
|
843
|
+
*/
|
|
844
|
+
autoStamps?: {
|
|
845
|
+
create?: string[];
|
|
846
|
+
update?: string[];
|
|
847
|
+
};
|
|
748
848
|
}
|
|
749
849
|
interface PatchResponse {
|
|
750
850
|
success: boolean;
|
|
751
851
|
id: string;
|
|
852
|
+
/** Server-applied field values (#1476). See `SaveResponse.appliedFields`. */
|
|
853
|
+
appliedFields?: Record<string, any>;
|
|
752
854
|
}
|
|
753
855
|
interface BatchOperation {
|
|
754
856
|
op: "save" | "patch" | "delete" | "increment" | "addToSet" | "removeFromSet";
|
|
@@ -760,15 +862,62 @@ interface BatchOperation {
|
|
|
760
862
|
ifNotExists?: boolean;
|
|
761
863
|
condition?: DocumentFilter;
|
|
762
864
|
upsertOn?: string;
|
|
865
|
+
/**
|
|
866
|
+
* Per-operation auto-timestamp directives. Same semantics as
|
|
867
|
+
* `SaveRequest.autoStamps` / `PatchRequest.autoStamps`. Applied
|
|
868
|
+
* BEFORE the user `beforeSave` hook runs, with explicit values
|
|
869
|
+
* in `data` always winning.
|
|
870
|
+
*
|
|
871
|
+
* For `op: "patch"`, only the `update` list applies (patches are
|
|
872
|
+
* always treated as updates).
|
|
873
|
+
*/
|
|
874
|
+
autoStamps?: {
|
|
875
|
+
create?: string[];
|
|
876
|
+
update?: string[];
|
|
877
|
+
};
|
|
763
878
|
}
|
|
764
879
|
interface BatchRequest {
|
|
765
880
|
operations: BatchOperation[];
|
|
881
|
+
/**
|
|
882
|
+
* All-or-nothing execution (#1478). When `true`, the FIRST operation that
|
|
883
|
+
* fails inside the transaction — hook denial, condition / `ifNotExists` /
|
|
884
|
+
* unique violation, record-not-found, or upsert validation error — aborts
|
|
885
|
+
* the whole batch: every write is rolled back and the handler returns an
|
|
886
|
+
* error naming the failed step with its natural HTTP status (see
|
|
887
|
+
* `BatchOperationResult.status`). When `false` or absent (the default), each
|
|
888
|
+
* operation's outcome is recorded independently and successful siblings
|
|
889
|
+
* commit — the partial-success behavior import-bulk and applyToQuery rely on.
|
|
890
|
+
*/
|
|
891
|
+
atomic?: boolean;
|
|
766
892
|
}
|
|
893
|
+
/**
|
|
894
|
+
* Stable, machine-readable failure tag on a failed `BatchOperationResult`
|
|
895
|
+
* (#1478). Derived at each failure site — never inferred from the `error`
|
|
896
|
+
* string. `#1389` (conditional writes + fencing-token enforcement) surfaces its
|
|
897
|
+
* condition / stale-token rejections through this same channel.
|
|
898
|
+
*/
|
|
899
|
+
type BatchFailureCode = "NOT_FOUND" | "HOOK_DENIED" | "CONDITION_NOT_MET" | "ALREADY_EXISTS" | "UNIQUE_VIOLATION" | "VALIDATION_ERROR";
|
|
767
900
|
interface BatchOperationResult {
|
|
768
901
|
success: boolean;
|
|
769
902
|
id: string;
|
|
770
903
|
error?: string;
|
|
904
|
+
/**
|
|
905
|
+
* Structured failure metadata (#1478), present only on a failed result. Both
|
|
906
|
+
* are derived at the failure site, not parsed back out of `error`:
|
|
907
|
+
* - `status` — the natural HTTP status for the cause (404 not found,
|
|
908
|
+
* 403 hook denial, 409 condition / `ifNotExists` / unique conflict,
|
|
909
|
+
* 400 upsert validation).
|
|
910
|
+
* - `code` — a stable machine-readable tag (see `BatchFailureCode`).
|
|
911
|
+
*
|
|
912
|
+
* In atomic mode the handler reads the first failure's `status`/`code` to
|
|
913
|
+
* build the error response, so an increment-on-missing op keeps its 404 and a
|
|
914
|
+
* hook denial keeps its 403 instead of collapsing to a uniform 409.
|
|
915
|
+
*/
|
|
916
|
+
status?: number;
|
|
917
|
+
code?: BatchFailureCode;
|
|
771
918
|
values?: Record<string, number>;
|
|
919
|
+
/** Server-applied field values (#1476). See `SaveResponse.appliedFields`. */
|
|
920
|
+
appliedFields?: Record<string, any>;
|
|
772
921
|
}
|
|
773
922
|
interface BatchResponse {
|
|
774
923
|
results: BatchOperationResult[];
|
package/dist/client.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;
|
|
@@ -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;
|
|
@@ -682,6 +738,25 @@ interface SaveRequest {
|
|
|
682
738
|
ifNotExists?: boolean;
|
|
683
739
|
condition?: DocumentFilter;
|
|
684
740
|
upsertOn?: string;
|
|
741
|
+
/**
|
|
742
|
+
* Per-call auto-timestamp directives for schemaless writes.
|
|
743
|
+
*
|
|
744
|
+
* Each entry is a list of field names to stamp with `Date.now()`
|
|
745
|
+
* (milliseconds) at the corresponding lifecycle event:
|
|
746
|
+
*
|
|
747
|
+
* - `create` — field is stamped only when the record is new
|
|
748
|
+
* (no row exists yet for this `modelName` + `id`).
|
|
749
|
+
* - `update` — field is stamped on every save (create AND update).
|
|
750
|
+
*
|
|
751
|
+
* If the caller already provided an explicit value for the field
|
|
752
|
+
* in `data`, the explicit value wins and the stamp is skipped.
|
|
753
|
+
* Stamping happens BEFORE the `beforeSave` user hook runs, so user
|
|
754
|
+
* hooks may still overwrite the stamped value if they wish.
|
|
755
|
+
*/
|
|
756
|
+
autoStamps?: {
|
|
757
|
+
create?: string[];
|
|
758
|
+
update?: string[];
|
|
759
|
+
};
|
|
685
760
|
}
|
|
686
761
|
interface DeleteRequest {
|
|
687
762
|
modelName: string;
|
|
@@ -704,6 +779,20 @@ interface QueryResponse {
|
|
|
704
779
|
interface SaveResponse {
|
|
705
780
|
success: boolean;
|
|
706
781
|
id: string;
|
|
782
|
+
/**
|
|
783
|
+
* Server-applied field values the DO wrote AFTER the caller's submission
|
|
784
|
+
* inputs were assembled, read back from the final post-hook `data`
|
|
785
|
+
* (#1476, consolidating the #1462 `stamps` channel). This is the single
|
|
786
|
+
* "server-applied fields" channel: it carries both the applied `autoStamps`
|
|
787
|
+
* timestamps and any fields a `beforeSave` hook reported via
|
|
788
|
+
* `serverAppliedFieldNames` (e.g. app-level trigger-computed values). Present
|
|
789
|
+
* only when at least one such field ended up with a value. Lets the Worker
|
|
790
|
+
* thread these values into the change-event `data` it broadcasts to
|
|
791
|
+
* subscribers — otherwise a `select`-listed stamped or trigger-computed field
|
|
792
|
+
* never reaches the frame, because the frame is rebuilt from the submission
|
|
793
|
+
* inputs (which predate the DO-side write).
|
|
794
|
+
*/
|
|
795
|
+
appliedFields?: Record<string, any>;
|
|
707
796
|
}
|
|
708
797
|
interface DeleteResponse {
|
|
709
798
|
success: boolean;
|
|
@@ -745,10 +834,23 @@ interface PatchRequest {
|
|
|
745
834
|
data: Record<string, any>;
|
|
746
835
|
stringSets?: Record<string, string[]>;
|
|
747
836
|
condition?: DocumentFilter;
|
|
837
|
+
/**
|
|
838
|
+
* Per-call auto-timestamp directives. A patch is always treated as
|
|
839
|
+
* an update (`isNew === false`), so only the `update` list is
|
|
840
|
+
* applied; the `create` list is ignored on patch.
|
|
841
|
+
*
|
|
842
|
+
* Explicit values in `data` win.
|
|
843
|
+
*/
|
|
844
|
+
autoStamps?: {
|
|
845
|
+
create?: string[];
|
|
846
|
+
update?: string[];
|
|
847
|
+
};
|
|
748
848
|
}
|
|
749
849
|
interface PatchResponse {
|
|
750
850
|
success: boolean;
|
|
751
851
|
id: string;
|
|
852
|
+
/** Server-applied field values (#1476). See `SaveResponse.appliedFields`. */
|
|
853
|
+
appliedFields?: Record<string, any>;
|
|
752
854
|
}
|
|
753
855
|
interface BatchOperation {
|
|
754
856
|
op: "save" | "patch" | "delete" | "increment" | "addToSet" | "removeFromSet";
|
|
@@ -760,15 +862,62 @@ interface BatchOperation {
|
|
|
760
862
|
ifNotExists?: boolean;
|
|
761
863
|
condition?: DocumentFilter;
|
|
762
864
|
upsertOn?: string;
|
|
865
|
+
/**
|
|
866
|
+
* Per-operation auto-timestamp directives. Same semantics as
|
|
867
|
+
* `SaveRequest.autoStamps` / `PatchRequest.autoStamps`. Applied
|
|
868
|
+
* BEFORE the user `beforeSave` hook runs, with explicit values
|
|
869
|
+
* in `data` always winning.
|
|
870
|
+
*
|
|
871
|
+
* For `op: "patch"`, only the `update` list applies (patches are
|
|
872
|
+
* always treated as updates).
|
|
873
|
+
*/
|
|
874
|
+
autoStamps?: {
|
|
875
|
+
create?: string[];
|
|
876
|
+
update?: string[];
|
|
877
|
+
};
|
|
763
878
|
}
|
|
764
879
|
interface BatchRequest {
|
|
765
880
|
operations: BatchOperation[];
|
|
881
|
+
/**
|
|
882
|
+
* All-or-nothing execution (#1478). When `true`, the FIRST operation that
|
|
883
|
+
* fails inside the transaction — hook denial, condition / `ifNotExists` /
|
|
884
|
+
* unique violation, record-not-found, or upsert validation error — aborts
|
|
885
|
+
* the whole batch: every write is rolled back and the handler returns an
|
|
886
|
+
* error naming the failed step with its natural HTTP status (see
|
|
887
|
+
* `BatchOperationResult.status`). When `false` or absent (the default), each
|
|
888
|
+
* operation's outcome is recorded independently and successful siblings
|
|
889
|
+
* commit — the partial-success behavior import-bulk and applyToQuery rely on.
|
|
890
|
+
*/
|
|
891
|
+
atomic?: boolean;
|
|
766
892
|
}
|
|
893
|
+
/**
|
|
894
|
+
* Stable, machine-readable failure tag on a failed `BatchOperationResult`
|
|
895
|
+
* (#1478). Derived at each failure site — never inferred from the `error`
|
|
896
|
+
* string. `#1389` (conditional writes + fencing-token enforcement) surfaces its
|
|
897
|
+
* condition / stale-token rejections through this same channel.
|
|
898
|
+
*/
|
|
899
|
+
type BatchFailureCode = "NOT_FOUND" | "HOOK_DENIED" | "CONDITION_NOT_MET" | "ALREADY_EXISTS" | "UNIQUE_VIOLATION" | "VALIDATION_ERROR";
|
|
767
900
|
interface BatchOperationResult {
|
|
768
901
|
success: boolean;
|
|
769
902
|
id: string;
|
|
770
903
|
error?: string;
|
|
904
|
+
/**
|
|
905
|
+
* Structured failure metadata (#1478), present only on a failed result. Both
|
|
906
|
+
* are derived at the failure site, not parsed back out of `error`:
|
|
907
|
+
* - `status` — the natural HTTP status for the cause (404 not found,
|
|
908
|
+
* 403 hook denial, 409 condition / `ifNotExists` / unique conflict,
|
|
909
|
+
* 400 upsert validation).
|
|
910
|
+
* - `code` — a stable machine-readable tag (see `BatchFailureCode`).
|
|
911
|
+
*
|
|
912
|
+
* In atomic mode the handler reads the first failure's `status`/`code` to
|
|
913
|
+
* build the error response, so an increment-on-missing op keeps its 404 and a
|
|
914
|
+
* hook denial keeps its 403 instead of collapsing to a uniform 409.
|
|
915
|
+
*/
|
|
916
|
+
status?: number;
|
|
917
|
+
code?: BatchFailureCode;
|
|
771
918
|
values?: Record<string, number>;
|
|
919
|
+
/** Server-applied field values (#1476). See `SaveResponse.appliedFields`. */
|
|
920
|
+
appliedFields?: Record<string, any>;
|
|
772
921
|
}
|
|
773
922
|
interface BatchResponse {
|
|
774
923
|
results: BatchOperationResult[];
|
package/dist/client.js
CHANGED
|
@@ -57,8 +57,40 @@ var init_DocumentQueryTranslator = __esm({
|
|
|
57
57
|
}
|
|
58
58
|
});
|
|
59
59
|
|
|
60
|
-
// src/
|
|
60
|
+
// src/utils/yjsValues.ts
|
|
61
61
|
import * as Y from "yjs";
|
|
62
|
+
var init_yjsValues = __esm({
|
|
63
|
+
"src/utils/yjsValues.ts"() {
|
|
64
|
+
"use strict";
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// src/models/writeLifecycle.ts
|
|
69
|
+
import * as Y2 from "yjs";
|
|
70
|
+
import { ulid } from "ulid";
|
|
71
|
+
function registerFunctionDefaultGenerator(name, fn) {
|
|
72
|
+
FUNCTION_DEFAULT_GENERATORS.set(name, fn);
|
|
73
|
+
}
|
|
74
|
+
var KNOWN_FIELD_TYPE_MAP, KNOWN_FIELD_TYPES, FUNCTION_DEFAULT_GENERATORS;
|
|
75
|
+
var init_writeLifecycle = __esm({
|
|
76
|
+
"src/models/writeLifecycle.ts"() {
|
|
77
|
+
"use strict";
|
|
78
|
+
KNOWN_FIELD_TYPE_MAP = {
|
|
79
|
+
string: true,
|
|
80
|
+
number: true,
|
|
81
|
+
boolean: true,
|
|
82
|
+
date: true,
|
|
83
|
+
id: true,
|
|
84
|
+
stringset: true
|
|
85
|
+
};
|
|
86
|
+
KNOWN_FIELD_TYPES = new Set(Object.keys(KNOWN_FIELD_TYPE_MAP));
|
|
87
|
+
FUNCTION_DEFAULT_GENERATORS = /* @__PURE__ */ new Map();
|
|
88
|
+
registerFunctionDefaultGenerator("generate_ulid", () => ulid());
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// src/models/metaSync.ts
|
|
93
|
+
import * as Y3 from "yjs";
|
|
62
94
|
function registerFunctionDefault(fn, name) {
|
|
63
95
|
KNOWN_FUNCTION_DEFAULTS.set(fn, name);
|
|
64
96
|
}
|
|
@@ -66,15 +98,25 @@ var KNOWN_FUNCTION_DEFAULTS;
|
|
|
66
98
|
var init_metaSync = __esm({
|
|
67
99
|
"src/models/metaSync.ts"() {
|
|
68
100
|
"use strict";
|
|
101
|
+
init_yjsValues();
|
|
102
|
+
init_writeLifecycle();
|
|
69
103
|
KNOWN_FUNCTION_DEFAULTS = /* @__PURE__ */ new WeakMap();
|
|
70
104
|
}
|
|
71
105
|
});
|
|
72
106
|
|
|
107
|
+
// src/utils/uniqueIndex.ts
|
|
108
|
+
import * as Y4 from "yjs";
|
|
109
|
+
var init_uniqueIndex = __esm({
|
|
110
|
+
"src/utils/uniqueIndex.ts"() {
|
|
111
|
+
"use strict";
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
73
115
|
// src/models/BaseModel.ts
|
|
74
|
-
import * as
|
|
75
|
-
import { ulid } from "ulid";
|
|
116
|
+
import * as Y5 from "yjs";
|
|
117
|
+
import { ulid as ulid2 } from "ulid";
|
|
76
118
|
function generateULID() {
|
|
77
|
-
return
|
|
119
|
+
return ulid2();
|
|
78
120
|
}
|
|
79
121
|
var Logger;
|
|
80
122
|
var init_BaseModel = __esm({
|
|
@@ -86,6 +128,9 @@ var init_BaseModel = __esm({
|
|
|
86
128
|
init_CursorManager();
|
|
87
129
|
init_sql();
|
|
88
130
|
init_metaSync();
|
|
131
|
+
init_writeLifecycle();
|
|
132
|
+
init_yjsValues();
|
|
133
|
+
init_uniqueIndex();
|
|
89
134
|
Logger = class {
|
|
90
135
|
static _logLevel = 1 /* ERROR */;
|
|
91
136
|
static _logCallback = null;
|