js-bao 0.7.0-alpha.9 → 0.11.1
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 +16 -0
- package/dist/browser.cjs +316 -55
- package/dist/browser.d.cts +170 -2
- package/dist/browser.d.ts +170 -2
- package/dist/browser.js +316 -54
- package/dist/client.cjs +1 -0
- package/dist/client.d.cts +25 -1
- package/dist/client.d.ts +25 -1
- package/dist/client.js +1 -0
- package/dist/cloudflare-do.cjs +462 -110
- package/dist/cloudflare-do.d.cts +242 -3
- package/dist/cloudflare-do.d.ts +242 -3
- package/dist/cloudflare-do.js +462 -109
- package/dist/cloudflare.cjs +112 -9
- package/dist/cloudflare.d.cts +125 -2
- package/dist/cloudflare.d.ts +125 -2
- package/dist/cloudflare.js +112 -8
- package/dist/codegen-v2.cjs +83 -9
- package/dist/codegen.cjs +1 -1
- package/dist/format2-worker.js +57 -10
- package/dist/index.cjs +324 -55
- package/dist/index.d.cts +170 -2
- package/dist/index.d.ts +170 -2
- package/dist/index.js +318 -54
- package/dist/node.cjs +322 -55
- package/dist/node.d.cts +170 -2
- package/dist/node.d.ts +170 -2
- package/dist/node.js +316 -54
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1553,3 +1553,19 @@ See the `examples/` directory for complete working examples:
|
|
|
1553
1553
|
|
|
1554
1554
|
- `examples/simple-node-test.mjs` - Basic Node.js usage without models
|
|
1555
1555
|
- `examples/node-example.mjs` - Complete Node.js example with models
|
|
1556
|
+
|
|
1557
|
+
## Release notes
|
|
1558
|
+
|
|
1559
|
+
Changes to the on-disk behaviour of `js-bao/cloudflare/do` (`DurableObjectEngine`)
|
|
1560
|
+
that a store's owner can observe. Newest first.
|
|
1561
|
+
|
|
1562
|
+
- **`_indexes.created_at` reports the first registration** (#3875). Re-registering
|
|
1563
|
+
a field index whose `field_type` and `is_unique` are unchanged no longer
|
|
1564
|
+
rewrites its `_indexes` row: it writes no row, and `created_at` keeps the time
|
|
1565
|
+
of the first registration. A registration that changes either column still
|
|
1566
|
+
rewrites the row and refreshes `created_at`, as before.
|
|
1567
|
+
- **`replaceWithStringSets` is internal** (#3875). The engine gained
|
|
1568
|
+
`replaceWithStringSets` / `replaceWithStringSetsRaw`, a guarded replace used
|
|
1569
|
+
only by the platform's plain-document projection rebuild. They are marked
|
|
1570
|
+
`@internal` and are not a public API; saves keep the merging
|
|
1571
|
+
`insertWithStringSets`.
|
package/dist/browser.cjs
CHANGED
|
@@ -1410,12 +1410,18 @@ function fieldUniqueConstraintName(modelName, fieldName) {
|
|
|
1410
1410
|
}
|
|
1411
1411
|
function resolveUniqueConstraints(modelName, fields, customConstraints) {
|
|
1412
1412
|
const resolved = [];
|
|
1413
|
+
const stringSetFields = /* @__PURE__ */ new Set();
|
|
1414
|
+
for (const [fieldName, options] of fields.entries()) {
|
|
1415
|
+
if (options.type === "stringset") stringSetFields.add(fieldName);
|
|
1416
|
+
}
|
|
1413
1417
|
for (const [fieldName, options] of fields.entries()) {
|
|
1414
1418
|
if (options.unique) {
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
+
const name = fieldUniqueConstraintName(modelName, fieldName);
|
|
1420
|
+
if (stringSetFields.has(fieldName)) {
|
|
1421
|
+
warnStringsetConstraintSkipped(modelName, name, fieldName);
|
|
1422
|
+
continue;
|
|
1423
|
+
}
|
|
1424
|
+
resolved.push({ name, fields: [fieldName] });
|
|
1419
1425
|
}
|
|
1420
1426
|
}
|
|
1421
1427
|
if (customConstraints) {
|
|
@@ -1429,6 +1435,13 @@ function resolveUniqueConstraints(modelName, fields, customConstraints) {
|
|
|
1429
1435
|
);
|
|
1430
1436
|
continue;
|
|
1431
1437
|
}
|
|
1438
|
+
const stringSetField = constraint.fields.find(
|
|
1439
|
+
(field) => stringSetFields.has(field)
|
|
1440
|
+
);
|
|
1441
|
+
if (stringSetField) {
|
|
1442
|
+
warnStringsetConstraintSkipped(modelName, constraint.name, stringSetField);
|
|
1443
|
+
continue;
|
|
1444
|
+
}
|
|
1432
1445
|
if (resolved.some((item) => item.name === constraint.name)) {
|
|
1433
1446
|
console.warn(
|
|
1434
1447
|
`[defineModelSchema] Duplicate unique constraint name "${constraint.name}" in model "${modelName}".`
|
|
@@ -1442,11 +1455,65 @@ function resolveUniqueConstraints(modelName, fields, customConstraints) {
|
|
|
1442
1455
|
}
|
|
1443
1456
|
return resolved;
|
|
1444
1457
|
}
|
|
1445
|
-
|
|
1458
|
+
function warnStringsetConstraintSkipped(modelName, constraintName, fieldName) {
|
|
1459
|
+
console.warn(
|
|
1460
|
+
`[defineModelSchema] Unique constraint "${constraintName}" for model "${modelName}" names the stringset field "${fieldName}", which cannot be unique. Skipping.`
|
|
1461
|
+
);
|
|
1462
|
+
}
|
|
1463
|
+
function uniqueStringsetViolations(modelName, fields, customConstraints) {
|
|
1464
|
+
const entries = fields instanceof Map ? [...fields.entries()] : Object.entries(fields);
|
|
1465
|
+
const stringSetFields = /* @__PURE__ */ new Set();
|
|
1466
|
+
const violations = [];
|
|
1467
|
+
for (const [fieldName, options] of entries) {
|
|
1468
|
+
if (options?.type !== "stringset") continue;
|
|
1469
|
+
stringSetFields.add(fieldName);
|
|
1470
|
+
if (options.unique === true) {
|
|
1471
|
+
violations.push({
|
|
1472
|
+
kind: "field",
|
|
1473
|
+
field: fieldName,
|
|
1474
|
+
constraint: fieldUniqueConstraintName(modelName, fieldName)
|
|
1475
|
+
});
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
for (const constraint of customConstraints ?? []) {
|
|
1479
|
+
const field = (constraint?.fields ?? []).find(
|
|
1480
|
+
(name) => stringSetFields.has(name)
|
|
1481
|
+
);
|
|
1482
|
+
if (field !== void 0) {
|
|
1483
|
+
violations.push({ kind: "compound", field, constraint: constraint.name });
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
return violations;
|
|
1487
|
+
}
|
|
1488
|
+
function uniqueStringsetMessage(modelName, violation) {
|
|
1489
|
+
const reason = "A unique constraint applies to scalar fields only.";
|
|
1490
|
+
if (violation.kind === "field") {
|
|
1491
|
+
return `Model "${modelName}": field "${violation.field}" is a stringset and cannot be unique. ${reason}`;
|
|
1492
|
+
}
|
|
1493
|
+
return `Model "${modelName}": unique constraint "${violation.constraint}" names the stringset field "${violation.field}", which cannot be unique. ${reason}`;
|
|
1494
|
+
}
|
|
1495
|
+
function omitStringsetConstraints(constraints, stringSetFields) {
|
|
1496
|
+
const stringSets = new Set(stringSetFields);
|
|
1497
|
+
const kept = [];
|
|
1498
|
+
const dropped = [];
|
|
1499
|
+
for (const constraint of constraints) {
|
|
1500
|
+
const field = (constraint.fields ?? []).find((name) => stringSets.has(name));
|
|
1501
|
+
if (field === void 0) kept.push(constraint);
|
|
1502
|
+
else dropped.push({ constraint, field });
|
|
1503
|
+
}
|
|
1504
|
+
return { kept, dropped };
|
|
1505
|
+
}
|
|
1506
|
+
var Y2, UniqueStringsetError;
|
|
1446
1507
|
var init_uniqueIndex = __esm({
|
|
1447
1508
|
"src/utils/uniqueIndex.ts"() {
|
|
1448
1509
|
"use strict";
|
|
1449
1510
|
Y2 = __toESM(require("yjs"), 1);
|
|
1511
|
+
UniqueStringsetError = class extends Error {
|
|
1512
|
+
constructor(message) {
|
|
1513
|
+
super(message);
|
|
1514
|
+
this.name = "UniqueStringsetError";
|
|
1515
|
+
}
|
|
1516
|
+
};
|
|
1450
1517
|
}
|
|
1451
1518
|
});
|
|
1452
1519
|
|
|
@@ -1727,6 +1794,80 @@ function groupOverlayEntries(entries) {
|
|
|
1727
1794
|
function readOverlayRecord(modelMap, recordId) {
|
|
1728
1795
|
return collectOverlayRecords(modelMap, /* @__PURE__ */ new Set([recordId])).get(recordId);
|
|
1729
1796
|
}
|
|
1797
|
+
function recordSegmentOf(key) {
|
|
1798
|
+
const separator = key.indexOf(OVERLAY_KEY_SEPARATOR);
|
|
1799
|
+
return separator > 0 ? key.slice(0, separator) : null;
|
|
1800
|
+
}
|
|
1801
|
+
function reindexKey(index, map, key) {
|
|
1802
|
+
const segment = recordSegmentOf(key);
|
|
1803
|
+
if (segment === null) return;
|
|
1804
|
+
if (map.has(key)) {
|
|
1805
|
+
let keys = index.get(segment);
|
|
1806
|
+
if (!keys) index.set(segment, keys = /* @__PURE__ */ new Set());
|
|
1807
|
+
keys.add(key);
|
|
1808
|
+
} else {
|
|
1809
|
+
const keys = index.get(segment);
|
|
1810
|
+
if (!keys) return;
|
|
1811
|
+
keys.delete(key);
|
|
1812
|
+
if (keys.size === 0) index.delete(segment);
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
function recordKeyIndexOf(source) {
|
|
1816
|
+
const map = source;
|
|
1817
|
+
if (typeof map.observe !== "function" || typeof map.has !== "function" || typeof map.get !== "function" || typeof map.keys !== "function") {
|
|
1818
|
+
return null;
|
|
1819
|
+
}
|
|
1820
|
+
const live = map;
|
|
1821
|
+
let index = recordKeyIndexes.get(source);
|
|
1822
|
+
if (!index) {
|
|
1823
|
+
const built = /* @__PURE__ */ new Map();
|
|
1824
|
+
for (const key of live.keys()) {
|
|
1825
|
+
if (typeof key === "string") reindexKey(built, live, key);
|
|
1826
|
+
}
|
|
1827
|
+
live.observe((event) => {
|
|
1828
|
+
for (const key of event.keysChanged) {
|
|
1829
|
+
if (typeof key === "string") reindexKey(built, live, key);
|
|
1830
|
+
}
|
|
1831
|
+
});
|
|
1832
|
+
recordKeyIndexes.set(source, built);
|
|
1833
|
+
index = built;
|
|
1834
|
+
}
|
|
1835
|
+
const pending = live.doc?._transaction?.changed.get(source);
|
|
1836
|
+
if (pending) {
|
|
1837
|
+
for (const key of pending) {
|
|
1838
|
+
if (typeof key === "string") reindexKey(index, live, key);
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
return index;
|
|
1842
|
+
}
|
|
1843
|
+
function foldTouchedOverlayKeys(modelMap, touched) {
|
|
1844
|
+
const present = [];
|
|
1845
|
+
const vanished = /* @__PURE__ */ new Set();
|
|
1846
|
+
const tombstoneTouched = /* @__PURE__ */ new Set();
|
|
1847
|
+
for (const key of touched) {
|
|
1848
|
+
const parsed = parseOverlayKey(key);
|
|
1849
|
+
if (!parsed) continue;
|
|
1850
|
+
if (parsed.marker === OVERLAY_MARKER_DELETED) tombstoneTouched.add(parsed.recordId);
|
|
1851
|
+
if (modelMap.has(key)) present.push([key, modelMap.get(key)]);
|
|
1852
|
+
else vanished.add(parsed.recordId);
|
|
1853
|
+
}
|
|
1854
|
+
const entries = groupOverlayEntries(present);
|
|
1855
|
+
for (const id of vanished) if (!entries.has(id)) entries.set(id, emptyEntry(id));
|
|
1856
|
+
const rebuild = /* @__PURE__ */ new Set();
|
|
1857
|
+
for (const [id, entry] of entries) {
|
|
1858
|
+
if (modelMap.get(overlayMarkerKey(id, OVERLAY_MARKER_DELETED)) === true) {
|
|
1859
|
+
entries.set(id, { ...emptyEntry(id), deleted: true });
|
|
1860
|
+
continue;
|
|
1861
|
+
}
|
|
1862
|
+
if (entry.replace || vanished.has(id) || tombstoneTouched.has(id)) rebuild.add(id);
|
|
1863
|
+
}
|
|
1864
|
+
if (rebuild.size > 0) {
|
|
1865
|
+
for (const [id, entry] of collectOverlayRecords(modelMap, rebuild)) {
|
|
1866
|
+
entries.set(id, entry);
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
return entries;
|
|
1870
|
+
}
|
|
1730
1871
|
function completeOverlayEntries(modelMap, entries) {
|
|
1731
1872
|
const rebuild = /* @__PURE__ */ new Set();
|
|
1732
1873
|
for (const [id, entry] of entries) {
|
|
@@ -1743,6 +1884,23 @@ function collectOverlayRecords(modelMap, recordIds) {
|
|
|
1743
1884
|
const wanted = /* @__PURE__ */ new Map();
|
|
1744
1885
|
for (const id of recordIds) wanted.set(encodeOverlaySegment(id), id);
|
|
1745
1886
|
const own = /* @__PURE__ */ new Map();
|
|
1887
|
+
const index = recordKeyIndexOf(modelMap);
|
|
1888
|
+
if (index) {
|
|
1889
|
+
const map = modelMap;
|
|
1890
|
+
for (const [segment, id] of wanted) {
|
|
1891
|
+
for (const key of index.get(segment) ?? []) {
|
|
1892
|
+
if (!map.has(key) || !parseOverlayKey(key)) continue;
|
|
1893
|
+
const keys = own.get(id);
|
|
1894
|
+
if (keys) keys.push([key, map.get(key)]);
|
|
1895
|
+
else own.set(id, [[key, map.get(key)]]);
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
const out2 = /* @__PURE__ */ new Map();
|
|
1899
|
+
for (const id of recordIds) {
|
|
1900
|
+
out2.set(id, groupOverlayEntries(own.get(id) ?? []).get(id) ?? emptyEntry(id));
|
|
1901
|
+
}
|
|
1902
|
+
return out2;
|
|
1903
|
+
}
|
|
1746
1904
|
for (const [key, value] of modelMap.entries()) {
|
|
1747
1905
|
if (typeof key !== "string") continue;
|
|
1748
1906
|
const separator = key.indexOf(OVERLAY_KEY_SEPARATOR);
|
|
@@ -1827,7 +1985,9 @@ function clearStaleRecordOverlay(modelMap, recordId, keep) {
|
|
|
1827
1985
|
if (!modelMap.has(overlayMarkerKey(recordId, OVERLAY_MARKER_DELETED))) return;
|
|
1828
1986
|
const prefix = overlayRecordPrefix(recordId);
|
|
1829
1987
|
const stale = [];
|
|
1830
|
-
|
|
1988
|
+
const index = recordKeyIndexOf(modelMap);
|
|
1989
|
+
const candidates = index ? index.get(encodeOverlaySegment(recordId)) ?? [] : modelMap.keys();
|
|
1990
|
+
for (const key of candidates) {
|
|
1831
1991
|
if (key.startsWith(prefix) && keep?.has(key) !== true) stale.push(key);
|
|
1832
1992
|
}
|
|
1833
1993
|
for (const key of stale) modelMap.delete(key);
|
|
@@ -1851,7 +2011,7 @@ function format2StringSetMembers(value) {
|
|
|
1851
2011
|
if (!(value instanceof Y5.Map)) return [];
|
|
1852
2012
|
return Array.from(value.entries()).filter(([, present]) => present === true).map(([member]) => member);
|
|
1853
2013
|
}
|
|
1854
|
-
var Y5, OVERLAY_KEY_SEPARATOR, OVERLAY_MARKER_REPLACE, OVERLAY_MARKER_DELETED, OVERLAY_MARKERS, Format2CompositeFieldError;
|
|
2014
|
+
var Y5, OVERLAY_KEY_SEPARATOR, OVERLAY_MARKER_REPLACE, OVERLAY_MARKER_DELETED, OVERLAY_MARKERS, recordKeyIndexes, Format2CompositeFieldError;
|
|
1855
2015
|
var init_largeDocuments = __esm({
|
|
1856
2016
|
"src/utils/largeDocuments.ts"() {
|
|
1857
2017
|
"use strict";
|
|
@@ -1864,6 +2024,7 @@ var init_largeDocuments = __esm({
|
|
|
1864
2024
|
OVERLAY_MARKER_REPLACE,
|
|
1865
2025
|
OVERLAY_MARKER_DELETED
|
|
1866
2026
|
]);
|
|
2027
|
+
recordKeyIndexes = /* @__PURE__ */ new WeakMap();
|
|
1867
2028
|
Format2CompositeFieldError = class extends Error {
|
|
1868
2029
|
modelName;
|
|
1869
2030
|
recordId;
|
|
@@ -2243,6 +2404,7 @@ var FORMAT2_MANIFEST_VERSION, FORMAT2_MIN_MANIFEST_VERSION, INGEST_CHUNK_LIMITS,
|
|
|
2243
2404
|
var init_snapshotChunks = __esm({
|
|
2244
2405
|
"src/utils/snapshotChunks.ts"() {
|
|
2245
2406
|
"use strict";
|
|
2407
|
+
init_uniqueIndex();
|
|
2246
2408
|
FORMAT2_MANIFEST_VERSION = 3;
|
|
2247
2409
|
FORMAT2_MIN_MANIFEST_VERSION = 2;
|
|
2248
2410
|
INGEST_CHUNK_LIMITS = {
|
|
@@ -2313,16 +2475,26 @@ function statementsFor(tables) {
|
|
|
2313
2475
|
deleteMember: `DELETE FROM ${members} WHERE _type = ? AND _record_id = ? AND field = ? AND value = ?`,
|
|
2314
2476
|
insertMember: `INSERT OR IGNORE INTO ${members} (_record_id, _type, field, value) VALUES (?, ?, ?, ?)`,
|
|
2315
2477
|
selectMembers: `SELECT value FROM ${members} WHERE _type = ? AND _record_id = ? AND field = ? ORDER BY rowid`,
|
|
2478
|
+
// `field, rowid`, not `rowid` alone: it is the order the per-record index
|
|
2479
|
+
// `(_type, _record_id, field)` yields, so the planner reads the record's
|
|
2480
|
+
// rows through it. With a bare `rowid` the covering UNIQUE
|
|
2481
|
+
// `(_type, field, _record_id, value)` wins on its `_type` prefix and walks
|
|
2482
|
+
// every member row of the model per record (#3688's figure, broken by
|
|
2483
|
+
// #3874). Stored order within a field, which is all the comparison reads,
|
|
2484
|
+
// is unchanged.
|
|
2485
|
+
selectAllMembers: `SELECT field, value FROM ${members} WHERE _type = ? AND _record_id = ? ORDER BY field, rowid`,
|
|
2316
2486
|
mergeUpsert: `
|
|
2317
2487
|
INSERT INTO ${records} (_id, _type, _data)
|
|
2318
2488
|
VALUES (?, ?, json_patch('{}', ?))
|
|
2319
2489
|
ON CONFLICT(_type, _id) DO UPDATE SET
|
|
2320
|
-
_data = json_patch(${records}._data, ?)
|
|
2490
|
+
_data = json_patch(${records}._data, ?)
|
|
2491
|
+
WHERE json_patch(${records}._data, ?) IS NOT ${records}._data`.trim(),
|
|
2321
2492
|
replaceUpsert: `
|
|
2322
2493
|
INSERT INTO ${records} (_id, _type, _data)
|
|
2323
2494
|
VALUES (?, ?, json_patch('{}', ?))
|
|
2324
2495
|
ON CONFLICT(_type, _id) DO UPDATE SET
|
|
2325
|
-
_data = json_patch('{}', ?)
|
|
2496
|
+
_data = json_patch('{}', ?)
|
|
2497
|
+
WHERE json_patch('{}', ?) IS NOT ${records}._data`.trim()
|
|
2326
2498
|
};
|
|
2327
2499
|
statementCache.set(cacheKey, statements);
|
|
2328
2500
|
return statements;
|
|
@@ -2335,6 +2507,7 @@ function projectOverlayEntry(sql, modelName, entry, tables = DEFAULT_OVERLAY_TAB
|
|
|
2335
2507
|
deleteMember: DELETE_MEMBER,
|
|
2336
2508
|
insertMember: INSERT_MEMBER,
|
|
2337
2509
|
selectMembers: SELECT_MEMBERS,
|
|
2510
|
+
selectAllMembers: SELECT_ALL_MEMBERS,
|
|
2338
2511
|
mergeUpsert: MERGE_UPSERT,
|
|
2339
2512
|
replaceUpsert: REPLACE_UPSERT
|
|
2340
2513
|
} = statementsFor(tables);
|
|
@@ -2348,10 +2521,20 @@ function projectOverlayEntry(sql, modelName, entry, tables = DEFAULT_OVERLAY_TAB
|
|
|
2348
2521
|
if (!entry.replace && touchedFields.length === 0 && touchedSets.length === 0) {
|
|
2349
2522
|
return;
|
|
2350
2523
|
}
|
|
2524
|
+
const patch = { ...entry.fields, id };
|
|
2351
2525
|
if (entry.replace) {
|
|
2526
|
+
const unchanged = unchangedReplaceMembers(
|
|
2527
|
+
sql,
|
|
2528
|
+
SELECT_ALL_MEMBERS,
|
|
2529
|
+
modelName,
|
|
2530
|
+
entry
|
|
2531
|
+
);
|
|
2532
|
+
if (unchanged) {
|
|
2533
|
+
for (const [field, values] of unchanged) patch[field] = values;
|
|
2534
|
+
return upsert(sql, REPLACE_UPSERT, modelName, id, patch);
|
|
2535
|
+
}
|
|
2352
2536
|
sql.exec(DELETE_ALL_MEMBERS, modelName, id);
|
|
2353
2537
|
}
|
|
2354
|
-
const patch = { ...entry.fields, id };
|
|
2355
2538
|
for (const [field, members] of Object.entries(entry.stringSets)) {
|
|
2356
2539
|
for (const [member, present] of Object.entries(members)) {
|
|
2357
2540
|
if (present) sql.exec(INSERT_MEMBER, id, modelName, field, member);
|
|
@@ -2360,14 +2543,40 @@ function projectOverlayEntry(sql, modelName, entry, tables = DEFAULT_OVERLAY_TAB
|
|
|
2360
2543
|
const rows = Array.from(sql.exec(SELECT_MEMBERS, modelName, id, field));
|
|
2361
2544
|
patch[field] = rows.map((row) => row.value);
|
|
2362
2545
|
}
|
|
2546
|
+
upsert(sql, entry.replace ? REPLACE_UPSERT : MERGE_UPSERT, modelName, id, patch);
|
|
2547
|
+
}
|
|
2548
|
+
function upsert(sql, statement, modelName, id, patch) {
|
|
2363
2549
|
const patchJson = JSON.stringify(patch);
|
|
2364
|
-
sql.exec(
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2550
|
+
sql.exec(statement, id, modelName, patchJson, patchJson, patchJson);
|
|
2551
|
+
}
|
|
2552
|
+
function unchangedReplaceMembers(sql, selectAllMembers, modelName, entry) {
|
|
2553
|
+
const wanted = /* @__PURE__ */ new Map();
|
|
2554
|
+
let present = 0;
|
|
2555
|
+
for (const [field, members] of Object.entries(entry.stringSets)) {
|
|
2556
|
+
const values = Object.entries(members).filter(([, isPresent]) => isPresent).map(([member]) => member);
|
|
2557
|
+
wanted.set(field, values);
|
|
2558
|
+
present += values.length;
|
|
2559
|
+
}
|
|
2560
|
+
if (present === 0) return null;
|
|
2561
|
+
const current = /* @__PURE__ */ new Map();
|
|
2562
|
+
for (const row of sql.exec(selectAllMembers, modelName, entry.id)) {
|
|
2563
|
+
const field = String(row.field);
|
|
2564
|
+
const values = current.get(field);
|
|
2565
|
+
if (values) values.push(String(row.value));
|
|
2566
|
+
else current.set(field, [String(row.value)]);
|
|
2567
|
+
}
|
|
2568
|
+
if (current.size === 0) return null;
|
|
2569
|
+
for (const [field, values] of wanted) {
|
|
2570
|
+
const held = current.get(field) ?? [];
|
|
2571
|
+
if (held.length !== values.length) return null;
|
|
2572
|
+
for (let i = 0; i < values.length; i++) {
|
|
2573
|
+
if (held[i] !== values[i]) return null;
|
|
2574
|
+
}
|
|
2575
|
+
}
|
|
2576
|
+
for (const field of current.keys()) {
|
|
2577
|
+
if (!wanted.has(field)) return null;
|
|
2578
|
+
}
|
|
2579
|
+
return wanted;
|
|
2371
2580
|
}
|
|
2372
2581
|
var DEFAULT_OVERLAY_TABLES, statementCache;
|
|
2373
2582
|
var init_overlaySql = __esm({
|
|
@@ -4505,15 +4714,7 @@ function materializeOverlayEntries(doc, touched) {
|
|
|
4505
4714
|
const out = [];
|
|
4506
4715
|
for (const [modelName, keys] of touched) {
|
|
4507
4716
|
const modelMap = doc.getMap(modelName);
|
|
4508
|
-
const
|
|
4509
|
-
for (const key of keys) {
|
|
4510
|
-
if (!parseOverlayKey(key) || !modelMap.has(key)) continue;
|
|
4511
|
-
delta.push([key, modelMap.get(key)]);
|
|
4512
|
-
}
|
|
4513
|
-
const grouped = completeOverlayEntries(
|
|
4514
|
-
modelMap,
|
|
4515
|
-
groupOverlayEntries(delta)
|
|
4516
|
-
);
|
|
4717
|
+
const grouped = foldTouchedOverlayKeys(modelMap, keys);
|
|
4517
4718
|
for (const entry of grouped.values()) out.push({ model: modelName, entry });
|
|
4518
4719
|
}
|
|
4519
4720
|
return out;
|
|
@@ -12430,7 +12631,40 @@ var init_BaseModel = __esm({
|
|
|
12430
12631
|
return { sql, params, aliasMetadata };
|
|
12431
12632
|
}
|
|
12432
12633
|
/**
|
|
12433
|
-
*
|
|
12634
|
+
* Read one row's operation values, collapsing a single-operation aggregate
|
|
12635
|
+
* to the bare value of that operation.
|
|
12636
|
+
*
|
|
12637
|
+
* This is the platform's one aggregate leaf rule, shared with the DO engine's
|
|
12638
|
+
* `_extractOpValue` (`engines/cloudflare/createDocumentDO.ts`, behind
|
|
12639
|
+
* database aggregates, registered `aggregate` operations, workflow pipeline
|
|
12640
|
+
* steps and `connectDoDb` bindings) and its mirror `aggregateRowsToResult`
|
|
12641
|
+
* in the platform's `src/document-projection.ts` (the document HTTP
|
|
12642
|
+
* aggregate). These model statics used to collapse a lone `count` only and
|
|
12643
|
+
* keep `<type>_<field>` for a lone `sum`/`avg`/`min`/`max`, so the same
|
|
12644
|
+
* declared aggregation answered differently depending on the path taken;
|
|
12645
|
+
* they were the outlier and moved to the shared rule in #3076 (js-bao 0.10.0).
|
|
12646
|
+
*
|
|
12647
|
+
* Both result-shaping branches below call this, so the StringSet-facet path
|
|
12648
|
+
* and the general path cannot drift apart again. Pinned by
|
|
12649
|
+
* `cli/tests/unit/aggregate-shaper-divergence-3076.test.ts` and, end to end
|
|
12650
|
+
* against the HTTP aggregate, by
|
|
12651
|
+
* `tests/api/http/aggregate-shape-convergence-3076.test.ts`.
|
|
12652
|
+
*/
|
|
12653
|
+
static extractAggregationLeaf(row, operations) {
|
|
12654
|
+
const valueFor = (operation) => operation.type === "count" ? row.count : row[`${operation.type}_${operation.field}`];
|
|
12655
|
+
if (operations.length === 1) return valueFor(operations[0]);
|
|
12656
|
+
const leaf = {};
|
|
12657
|
+
for (const operation of operations) {
|
|
12658
|
+
if (operation.type === "count") leaf.count = row.count;
|
|
12659
|
+
else leaf[`${operation.type}_${operation.field}`] = valueFor(operation);
|
|
12660
|
+
}
|
|
12661
|
+
return leaf;
|
|
12662
|
+
}
|
|
12663
|
+
/**
|
|
12664
|
+
* Process aggregation results into nested structure.
|
|
12665
|
+
*
|
|
12666
|
+
* The leaf under the last group value follows the one platform rule — see
|
|
12667
|
+
* {@link BaseModel.extractAggregationLeaf}.
|
|
12434
12668
|
*/
|
|
12435
12669
|
static processAggregationResults(results, options, aliasMetadata) {
|
|
12436
12670
|
if (results.length === 0) {
|
|
@@ -12446,19 +12680,10 @@ var init_BaseModel = __esm({
|
|
|
12446
12680
|
if (options.groupBy.length === 1 && typeof options.groupBy[0] === "string" && results[0].hasOwnProperty("group_key")) {
|
|
12447
12681
|
const facetResult = {};
|
|
12448
12682
|
for (const row of results) {
|
|
12449
|
-
|
|
12450
|
-
|
|
12451
|
-
|
|
12452
|
-
|
|
12453
|
-
facetResult[key].count = row.count;
|
|
12454
|
-
} else {
|
|
12455
|
-
const operationKey = `${operation.type}_${operation.field}`;
|
|
12456
|
-
facetResult[key][operationKey] = row[operationKey];
|
|
12457
|
-
}
|
|
12458
|
-
}
|
|
12459
|
-
if (options.operations.length === 1 && options.operations[0].type === "count") {
|
|
12460
|
-
facetResult[key] = row.count;
|
|
12461
|
-
}
|
|
12683
|
+
facetResult[row.group_key] = this.extractAggregationLeaf(
|
|
12684
|
+
row,
|
|
12685
|
+
options.operations
|
|
12686
|
+
);
|
|
12462
12687
|
}
|
|
12463
12688
|
return facetResult;
|
|
12464
12689
|
}
|
|
@@ -12486,18 +12711,7 @@ var init_BaseModel = __esm({
|
|
|
12486
12711
|
key = row[resolvedAlias];
|
|
12487
12712
|
}
|
|
12488
12713
|
if (i === options.groupBy.length - 1) {
|
|
12489
|
-
current[key] =
|
|
12490
|
-
for (const operation of options.operations) {
|
|
12491
|
-
if (operation.type === "count") {
|
|
12492
|
-
current[key].count = row.count;
|
|
12493
|
-
} else {
|
|
12494
|
-
const operationKey = `${operation.type}_${operation.field}`;
|
|
12495
|
-
current[key][operationKey] = row[operationKey];
|
|
12496
|
-
}
|
|
12497
|
-
}
|
|
12498
|
-
if (options.operations.length === 1 && options.operations[0].type === "count") {
|
|
12499
|
-
current[key] = row.count;
|
|
12500
|
-
}
|
|
12714
|
+
current[key] = this.extractAggregationLeaf(row, options.operations);
|
|
12501
12715
|
} else {
|
|
12502
12716
|
if (!current[key]) {
|
|
12503
12717
|
current[key] = {};
|
|
@@ -13337,6 +13551,7 @@ __export(browser_exports, {
|
|
|
13337
13551
|
SqljsEngine: () => SqljsEngine,
|
|
13338
13552
|
StringSet: () => StringSet,
|
|
13339
13553
|
UniqueConstraintViolationError: () => UniqueConstraintViolationError,
|
|
13554
|
+
UniqueStringsetError: () => UniqueStringsetError,
|
|
13340
13555
|
applyFastForward: () => applyFastForward,
|
|
13341
13556
|
applyOverlayMutation: () => applyOverlayMutation,
|
|
13342
13557
|
assertNoFormat2CompositeFields: () => assertNoFormat2CompositeFields,
|
|
@@ -13364,7 +13579,9 @@ __export(browser_exports, {
|
|
|
13364
13579
|
estimateMaterializedBytes: () => estimateMaterializedBytes,
|
|
13365
13580
|
features: () => features,
|
|
13366
13581
|
findReservedFieldNameInToml: () => findReservedFieldNameInToml,
|
|
13582
|
+
findUniqueStringsetInToml: () => findUniqueStringsetInToml,
|
|
13367
13583
|
foldFormat2Overlay: () => foldFormat2Overlay,
|
|
13584
|
+
foldTouchedOverlayKeys: () => foldTouchedOverlayKeys,
|
|
13368
13585
|
followEpoch: () => followEpoch,
|
|
13369
13586
|
format2ClientDDL: () => format2ClientDDL,
|
|
13370
13587
|
format2EpochDocName: () => format2EpochDocName,
|
|
@@ -13391,6 +13608,7 @@ __export(browser_exports, {
|
|
|
13391
13608
|
observeFormat2Overlay: () => observeFormat2Overlay,
|
|
13392
13609
|
offlineReplayRecordKey: () => offlineReplayRecordKey,
|
|
13393
13610
|
offlineWindowStatus: () => offlineWindowStatus,
|
|
13611
|
+
omitStringsetConstraints: () => omitStringsetConstraints,
|
|
13394
13612
|
overlayFieldKey: () => overlayFieldKey,
|
|
13395
13613
|
overlayKeysSince: () => overlayKeysSince,
|
|
13396
13614
|
overlayMarkerKey: () => overlayMarkerKey,
|
|
@@ -13420,6 +13638,8 @@ __export(browser_exports, {
|
|
|
13420
13638
|
suppressSupersededKeys: () => suppressSupersededKeys,
|
|
13421
13639
|
syncInferredMeta: () => syncInferredMeta,
|
|
13422
13640
|
syncModelMeta: () => syncModelMeta,
|
|
13641
|
+
uniqueStringsetMessage: () => uniqueStringsetMessage,
|
|
13642
|
+
uniqueStringsetViolations: () => uniqueStringsetViolations,
|
|
13423
13643
|
validateSnapshotManifest: () => validateSnapshotManifest,
|
|
13424
13644
|
writeCarriedOverlay: () => writeCarriedOverlay
|
|
13425
13645
|
});
|
|
@@ -19395,6 +19615,17 @@ init_BaseModel();
|
|
|
19395
19615
|
init_ModelRegistry();
|
|
19396
19616
|
init_uniqueIndex();
|
|
19397
19617
|
function defineModelSchema(input) {
|
|
19618
|
+
const [violation] = uniqueStringsetViolations(
|
|
19619
|
+
input.name,
|
|
19620
|
+
input.fields,
|
|
19621
|
+
input.options?.uniqueConstraints
|
|
19622
|
+
);
|
|
19623
|
+
if (violation) {
|
|
19624
|
+
throw new UniqueStringsetError(uniqueStringsetMessage(input.name, violation));
|
|
19625
|
+
}
|
|
19626
|
+
return buildDefinedModelSchema(input);
|
|
19627
|
+
}
|
|
19628
|
+
function buildDefinedModelSchema(input) {
|
|
19398
19629
|
const { name, fields } = input;
|
|
19399
19630
|
const options = {
|
|
19400
19631
|
name,
|
|
@@ -19722,6 +19953,7 @@ var ReservedFieldNameError = class extends Error {
|
|
|
19722
19953
|
};
|
|
19723
19954
|
|
|
19724
19955
|
// src/models/tomlLoader.ts
|
|
19956
|
+
init_uniqueIndex();
|
|
19725
19957
|
var VALID_FIELD_TYPES = /* @__PURE__ */ new Set([
|
|
19726
19958
|
"string",
|
|
19727
19959
|
"number",
|
|
@@ -19869,6 +20101,7 @@ function parseRelationship(raw, context, strict) {
|
|
|
19869
20101
|
function loadSchemaFromTomlString(tomlString, options = {}) {
|
|
19870
20102
|
const strict = options.strict !== false;
|
|
19871
20103
|
const rejectReserved = options.rejectReservedFieldNames === true;
|
|
20104
|
+
const rejectUniqueStringset = options.rejectUniqueStringset !== false;
|
|
19872
20105
|
const parsed = (0, import_smol_toml.parse)(tomlString);
|
|
19873
20106
|
const models = parsed.models;
|
|
19874
20107
|
if (!models || typeof models !== "object") {
|
|
@@ -19883,6 +20116,7 @@ function loadSchemaFromTomlString(tomlString, options = {}) {
|
|
|
19883
20116
|
strict
|
|
19884
20117
|
);
|
|
19885
20118
|
const fields = {};
|
|
20119
|
+
const declaredFields = /* @__PURE__ */ new Map();
|
|
19886
20120
|
if (modelDef.fields) {
|
|
19887
20121
|
for (const [fieldName, fieldDef] of Object.entries(modelDef.fields)) {
|
|
19888
20122
|
const context = `[models.${modelName}.fields.${fieldName}]`;
|
|
@@ -19892,7 +20126,9 @@ function loadSchemaFromTomlString(tomlString, options = {}) {
|
|
|
19892
20126
|
throw new ReservedFieldNameError(`${context}: ${reserved}`);
|
|
19893
20127
|
}
|
|
19894
20128
|
}
|
|
19895
|
-
|
|
20129
|
+
const fieldOptions = parseFieldOptions(fieldDef, context, strict);
|
|
20130
|
+
declaredFields.set(fieldName, fieldOptions);
|
|
20131
|
+
fields[fieldName] = fieldOptions;
|
|
19896
20132
|
}
|
|
19897
20133
|
}
|
|
19898
20134
|
let relationships;
|
|
@@ -19925,8 +20161,20 @@ function loadSchemaFromTomlString(tomlString, options = {}) {
|
|
|
19925
20161
|
}
|
|
19926
20162
|
}
|
|
19927
20163
|
const className = typeof modelDef.class_name === "string" ? modelDef.class_name : void 0;
|
|
20164
|
+
if (rejectUniqueStringset) {
|
|
20165
|
+
const [violation] = uniqueStringsetViolations(
|
|
20166
|
+
modelName,
|
|
20167
|
+
declaredFields,
|
|
20168
|
+
uniqueConstraints
|
|
20169
|
+
);
|
|
20170
|
+
if (violation) {
|
|
20171
|
+
throw new UniqueStringsetError(
|
|
20172
|
+
uniqueStringsetMessage(modelName, violation)
|
|
20173
|
+
);
|
|
20174
|
+
}
|
|
20175
|
+
}
|
|
19928
20176
|
schemas.push(
|
|
19929
|
-
|
|
20177
|
+
buildDefinedModelSchema({
|
|
19930
20178
|
name: modelName,
|
|
19931
20179
|
fields,
|
|
19932
20180
|
options: {
|
|
@@ -19943,7 +20191,10 @@ function findReservedFieldNameInToml(tomlString) {
|
|
|
19943
20191
|
try {
|
|
19944
20192
|
loadSchemaFromTomlString(tomlString, {
|
|
19945
20193
|
strict: false,
|
|
19946
|
-
rejectReservedFieldNames: true
|
|
20194
|
+
rejectReservedFieldNames: true,
|
|
20195
|
+
// Independent of the #3719 check: a unique stringset in one model must
|
|
20196
|
+
// not hide a reserved name in the next.
|
|
20197
|
+
rejectUniqueStringset: false
|
|
19947
20198
|
});
|
|
19948
20199
|
return null;
|
|
19949
20200
|
} catch (error) {
|
|
@@ -19951,8 +20202,18 @@ function findReservedFieldNameInToml(tomlString) {
|
|
|
19951
20202
|
return null;
|
|
19952
20203
|
}
|
|
19953
20204
|
}
|
|
20205
|
+
function findUniqueStringsetInToml(tomlString) {
|
|
20206
|
+
try {
|
|
20207
|
+
loadSchemaFromTomlString(tomlString, { strict: false });
|
|
20208
|
+
return null;
|
|
20209
|
+
} catch (error) {
|
|
20210
|
+
if (error instanceof UniqueStringsetError) return error.message;
|
|
20211
|
+
return null;
|
|
20212
|
+
}
|
|
20213
|
+
}
|
|
19954
20214
|
|
|
19955
20215
|
// src/browser.ts
|
|
20216
|
+
init_uniqueIndex();
|
|
19956
20217
|
init_metaSync();
|
|
19957
20218
|
init_format2RecordStore();
|
|
19958
20219
|
init_format2RecordStoreAsync();
|