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-do.js
CHANGED
|
@@ -17,6 +17,21 @@ function assertValidIdentifier(name, context) {
|
|
|
17
17
|
function quoteIdentifier(name) {
|
|
18
18
|
return `"${name.replace(/"/g, '""')}"`;
|
|
19
19
|
}
|
|
20
|
+
function deterministicIdentifierHash(value) {
|
|
21
|
+
let hash = 0;
|
|
22
|
+
for (let i = 0; i < value.length; i++) {
|
|
23
|
+
hash = (hash << 5) - hash + value.charCodeAt(i);
|
|
24
|
+
hash |= 0;
|
|
25
|
+
}
|
|
26
|
+
const positiveHash = hash === 0 ? 0 : Math.abs(hash);
|
|
27
|
+
return positiveHash.toString(36);
|
|
28
|
+
}
|
|
29
|
+
function buildSafeAlias(prefix, rawDescriptor) {
|
|
30
|
+
const suffix = deterministicIdentifierHash(rawDescriptor);
|
|
31
|
+
const candidate = `${prefix}_${suffix}`;
|
|
32
|
+
assertValidIdentifier(candidate, "Alias generation");
|
|
33
|
+
return candidate;
|
|
34
|
+
}
|
|
20
35
|
var IDENTIFIER_PATTERN;
|
|
21
36
|
var init_sql = __esm({
|
|
22
37
|
"src/utils/sql.ts"() {
|
|
@@ -214,21 +229,36 @@ var init_CursorManager = __esm({
|
|
|
214
229
|
return fields;
|
|
215
230
|
}
|
|
216
231
|
/**
|
|
217
|
-
* Build ORDER BY clause from sort specification
|
|
232
|
+
* Build ORDER BY clause from sort specification.
|
|
233
|
+
*
|
|
234
|
+
* `direction` is the pagination direction (1 = forward, -1 = backward).
|
|
235
|
+
* For backward pagination each field's effective ORDER BY direction is
|
|
236
|
+
* reversed so the cursor's flipped inequality (built by
|
|
237
|
+
* `buildPaginationConditions`) returns the page adjacent to the cursor,
|
|
238
|
+
* not the globally smallest/largest window. Without this,
|
|
239
|
+
* `field < cursor ... ORDER BY field ASC LIMIT n` returns the n smallest
|
|
240
|
+
* rows overall instead of the n rows just below the cursor.
|
|
241
|
+
*
|
|
242
|
+
* The returned `directions` array carries the *declared* per-field sort
|
|
243
|
+
* directions (unaffected by `direction`) — `buildPaginationConditions`
|
|
244
|
+
* needs the declared directions to pick each field's comparison operator.
|
|
218
245
|
*/
|
|
219
|
-
static buildOrderClause(sort, fieldFormatter = (field) => field) {
|
|
246
|
+
static buildOrderClause(sort, fieldFormatter = (field) => field, direction = 1) {
|
|
220
247
|
const fields = this.extractSortFields(sort);
|
|
221
248
|
const clauses = [];
|
|
222
249
|
const directions = [];
|
|
250
|
+
const sqlDir = (declared) => {
|
|
251
|
+
const effective = direction === -1 ? declared === 1 ? -1 : 1 : declared;
|
|
252
|
+
return effective === 1 ? "ASC" : "DESC";
|
|
253
|
+
};
|
|
223
254
|
if (sort && Object.keys(sort).length > 0) {
|
|
224
255
|
for (const [field, dir] of Object.entries(sort)) {
|
|
225
|
-
|
|
226
|
-
clauses.push(`${fieldFormatter(field)} ${sqlDirection}`);
|
|
256
|
+
clauses.push(`${fieldFormatter(field)} ${sqlDir(dir)}`);
|
|
227
257
|
directions.push(dir);
|
|
228
258
|
}
|
|
229
259
|
}
|
|
230
260
|
if (!sort || !sort.hasOwnProperty("id")) {
|
|
231
|
-
clauses.push(`${fieldFormatter("id")}
|
|
261
|
+
clauses.push(`${fieldFormatter("id")} ${sqlDir(1)}`);
|
|
232
262
|
directions.push(1);
|
|
233
263
|
} else if (sort && sort.hasOwnProperty("id")) {
|
|
234
264
|
directions.push(sort["id"]);
|
|
@@ -251,18 +281,27 @@ var init_CursorManager = __esm({
|
|
|
251
281
|
/**
|
|
252
282
|
* Generate next and previous cursors from result set
|
|
253
283
|
*/
|
|
254
|
-
static generateResultCursors(results, sortFields,
|
|
284
|
+
static generateResultCursors(results, sortFields, requestedDirection, hasMore, isFirstPage = false) {
|
|
255
285
|
if (results.length === 0) {
|
|
256
286
|
return {};
|
|
257
287
|
}
|
|
258
288
|
const cursors = {};
|
|
259
289
|
if (hasMore && results.length > 0) {
|
|
260
290
|
const lastResult = results[results.length - 1];
|
|
261
|
-
cursors.nextCursor = this.generateCursor(
|
|
291
|
+
cursors.nextCursor = this.generateCursor(
|
|
292
|
+
lastResult,
|
|
293
|
+
sortFields,
|
|
294
|
+
requestedDirection
|
|
295
|
+
);
|
|
262
296
|
}
|
|
263
297
|
if (results.length > 0 && !isFirstPage) {
|
|
264
298
|
const firstResult = results[0];
|
|
265
|
-
|
|
299
|
+
const opposite = requestedDirection === 1 ? -1 : 1;
|
|
300
|
+
cursors.prevCursor = this.generateCursor(
|
|
301
|
+
firstResult,
|
|
302
|
+
sortFields,
|
|
303
|
+
opposite
|
|
304
|
+
);
|
|
266
305
|
}
|
|
267
306
|
return cursors;
|
|
268
307
|
}
|
|
@@ -312,6 +351,21 @@ var init_patterns = __esm({
|
|
|
312
351
|
}
|
|
313
352
|
});
|
|
314
353
|
|
|
354
|
+
// src/utils/yjsValues.ts
|
|
355
|
+
import * as Y from "yjs";
|
|
356
|
+
function isStringSetYMap(value) {
|
|
357
|
+
if (!(value instanceof Y.Map)) return false;
|
|
358
|
+
for (const v of value.values()) {
|
|
359
|
+
if (v !== true) return false;
|
|
360
|
+
}
|
|
361
|
+
return true;
|
|
362
|
+
}
|
|
363
|
+
var init_yjsValues = __esm({
|
|
364
|
+
"src/utils/yjsValues.ts"() {
|
|
365
|
+
"use strict";
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
|
|
315
369
|
// src/models/StringSet.ts
|
|
316
370
|
var init_StringSet = __esm({
|
|
317
371
|
"src/models/StringSet.ts"() {
|
|
@@ -337,10 +391,35 @@ var init_DocumentQueryTranslator = __esm({
|
|
|
337
391
|
}
|
|
338
392
|
});
|
|
339
393
|
|
|
394
|
+
// src/models/writeLifecycle.ts
|
|
395
|
+
import * as Y3 from "yjs";
|
|
396
|
+
import { ulid as ulid2 } from "ulid";
|
|
397
|
+
function registerFunctionDefaultGenerator(name, fn) {
|
|
398
|
+
FUNCTION_DEFAULT_GENERATORS.set(name, fn);
|
|
399
|
+
}
|
|
400
|
+
var KNOWN_FIELD_TYPE_MAP, KNOWN_FIELD_TYPES, FUNCTION_DEFAULT_GENERATORS, UNKNOWN_FUNCTION_DEFAULT;
|
|
401
|
+
var init_writeLifecycle = __esm({
|
|
402
|
+
"src/models/writeLifecycle.ts"() {
|
|
403
|
+
"use strict";
|
|
404
|
+
KNOWN_FIELD_TYPE_MAP = {
|
|
405
|
+
string: true,
|
|
406
|
+
number: true,
|
|
407
|
+
boolean: true,
|
|
408
|
+
date: true,
|
|
409
|
+
id: true,
|
|
410
|
+
stringset: true
|
|
411
|
+
};
|
|
412
|
+
KNOWN_FIELD_TYPES = new Set(Object.keys(KNOWN_FIELD_TYPE_MAP));
|
|
413
|
+
FUNCTION_DEFAULT_GENERATORS = /* @__PURE__ */ new Map();
|
|
414
|
+
registerFunctionDefaultGenerator("generate_ulid", () => ulid2());
|
|
415
|
+
UNKNOWN_FUNCTION_DEFAULT = "$unknown_function";
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
|
|
340
419
|
// src/models/metaSync.ts
|
|
341
|
-
import * as
|
|
420
|
+
import * as Y4 from "yjs";
|
|
342
421
|
function inferFieldType(value) {
|
|
343
|
-
if (value
|
|
422
|
+
if (isStringSetYMap(value)) return "stringset";
|
|
344
423
|
switch (typeof value) {
|
|
345
424
|
case "string":
|
|
346
425
|
return "string";
|
|
@@ -359,7 +438,10 @@ function encodeDefault(value) {
|
|
|
359
438
|
if (value === void 0 || value === null) return void 0;
|
|
360
439
|
if (typeof value === "function") {
|
|
361
440
|
const name = KNOWN_FUNCTION_DEFAULTS.get(value);
|
|
362
|
-
return name ? `$${name}` :
|
|
441
|
+
return name ? `$${name}` : UNKNOWN_FUNCTION_DEFAULT;
|
|
442
|
+
}
|
|
443
|
+
if (typeof value === "string" && value.startsWith("$")) {
|
|
444
|
+
return `$${value}`;
|
|
363
445
|
}
|
|
364
446
|
return value;
|
|
365
447
|
}
|
|
@@ -385,7 +467,7 @@ function syncModelMeta(yDoc, modelName, schema) {
|
|
|
385
467
|
if (compoundConstraints.length > 0) {
|
|
386
468
|
let constraints = meta.get("_constraints");
|
|
387
469
|
if (!constraints) {
|
|
388
|
-
constraints = new
|
|
470
|
+
constraints = new Y4.Map();
|
|
389
471
|
meta.set("_constraints", constraints);
|
|
390
472
|
}
|
|
391
473
|
for (const constraint of compoundConstraints) {
|
|
@@ -396,7 +478,7 @@ function syncModelMeta(yDoc, modelName, schema) {
|
|
|
396
478
|
if (relationships && Object.keys(relationships).length > 0) {
|
|
397
479
|
let rels = meta.get("_relationships");
|
|
398
480
|
if (!rels) {
|
|
399
|
-
rels = new
|
|
481
|
+
rels = new Y4.Map();
|
|
400
482
|
meta.set("_relationships", rels);
|
|
401
483
|
}
|
|
402
484
|
for (const [relName, relConfig] of Object.entries(relationships)) {
|
|
@@ -408,7 +490,7 @@ function syncModelMeta(yDoc, modelName, schema) {
|
|
|
408
490
|
function syncFieldMeta(metaMap, fieldName, fieldOpts) {
|
|
409
491
|
let fieldMeta = metaMap.get(fieldName);
|
|
410
492
|
if (!fieldMeta) {
|
|
411
|
-
fieldMeta = new
|
|
493
|
+
fieldMeta = new Y4.Map();
|
|
412
494
|
metaMap.set(fieldName, fieldMeta);
|
|
413
495
|
}
|
|
414
496
|
setIfChanged(fieldMeta, "type", fieldOpts.type);
|
|
@@ -416,6 +498,7 @@ function syncFieldMeta(metaMap, fieldName, fieldOpts) {
|
|
|
416
498
|
if (fieldOpts.unique) setIfChanged(fieldMeta, "unique", true);
|
|
417
499
|
if (fieldOpts.required) setIfChanged(fieldMeta, "required", true);
|
|
418
500
|
if (fieldOpts.autoAssign) setIfChanged(fieldMeta, "autoAssign", true);
|
|
501
|
+
if (fieldOpts.autoStamp) setIfChanged(fieldMeta, "autoStamp", fieldOpts.autoStamp);
|
|
419
502
|
if (fieldOpts.maxLength !== void 0) setIfChanged(fieldMeta, "maxLength", fieldOpts.maxLength);
|
|
420
503
|
if (fieldOpts.maxCount !== void 0) setIfChanged(fieldMeta, "maxCount", fieldOpts.maxCount);
|
|
421
504
|
const encoded = encodeDefault(fieldOpts.default);
|
|
@@ -424,7 +507,7 @@ function syncFieldMeta(metaMap, fieldName, fieldOpts) {
|
|
|
424
507
|
function syncConstraintMeta(constraintsMap, constraint) {
|
|
425
508
|
let cMeta = constraintsMap.get(constraint.name);
|
|
426
509
|
if (!cMeta) {
|
|
427
|
-
cMeta = new
|
|
510
|
+
cMeta = new Y4.Map();
|
|
428
511
|
constraintsMap.set(constraint.name, cMeta);
|
|
429
512
|
}
|
|
430
513
|
setIfChanged(cMeta, "type", "unique");
|
|
@@ -434,7 +517,7 @@ function syncConstraintMeta(constraintsMap, constraint) {
|
|
|
434
517
|
function syncRelationshipMeta(relsMap, relName, relConfig) {
|
|
435
518
|
let relMeta = relsMap.get(relName);
|
|
436
519
|
if (!relMeta) {
|
|
437
|
-
relMeta = new
|
|
520
|
+
relMeta = new Y4.Map();
|
|
438
521
|
relsMap.set(relName, relMeta);
|
|
439
522
|
}
|
|
440
523
|
for (const [key, value] of Object.entries(relConfig)) {
|
|
@@ -449,7 +532,7 @@ function syncInferredMeta(yDoc, modelName, recordData) {
|
|
|
449
532
|
if (fieldName.startsWith("_")) continue;
|
|
450
533
|
let fieldMeta = meta.get(fieldName);
|
|
451
534
|
if (!fieldMeta) {
|
|
452
|
-
fieldMeta = new
|
|
535
|
+
fieldMeta = new Y4.Map();
|
|
453
536
|
meta.set(fieldName, fieldMeta);
|
|
454
537
|
}
|
|
455
538
|
if (!fieldMeta.has("type")) {
|
|
@@ -469,16 +552,29 @@ var KNOWN_FUNCTION_DEFAULTS, _syncedCache;
|
|
|
469
552
|
var init_metaSync = __esm({
|
|
470
553
|
"src/models/metaSync.ts"() {
|
|
471
554
|
"use strict";
|
|
555
|
+
init_yjsValues();
|
|
556
|
+
init_writeLifecycle();
|
|
472
557
|
KNOWN_FUNCTION_DEFAULTS = /* @__PURE__ */ new WeakMap();
|
|
473
558
|
_syncedCache = /* @__PURE__ */ new WeakMap();
|
|
474
559
|
}
|
|
475
560
|
});
|
|
476
561
|
|
|
562
|
+
// src/utils/uniqueIndex.ts
|
|
563
|
+
import * as Y5 from "yjs";
|
|
564
|
+
function fieldUniqueConstraintName(modelName, fieldName) {
|
|
565
|
+
return `${modelName}_${fieldName}_unique`;
|
|
566
|
+
}
|
|
567
|
+
var init_uniqueIndex = __esm({
|
|
568
|
+
"src/utils/uniqueIndex.ts"() {
|
|
569
|
+
"use strict";
|
|
570
|
+
}
|
|
571
|
+
});
|
|
572
|
+
|
|
477
573
|
// src/models/BaseModel.ts
|
|
478
|
-
import * as
|
|
479
|
-
import { ulid as
|
|
574
|
+
import * as Y6 from "yjs";
|
|
575
|
+
import { ulid as ulid3 } from "ulid";
|
|
480
576
|
function generateULID() {
|
|
481
|
-
return
|
|
577
|
+
return ulid3();
|
|
482
578
|
}
|
|
483
579
|
var init_BaseModel = __esm({
|
|
484
580
|
"src/models/BaseModel.ts"() {
|
|
@@ -489,6 +585,9 @@ var init_BaseModel = __esm({
|
|
|
489
585
|
init_CursorManager();
|
|
490
586
|
init_sql();
|
|
491
587
|
init_metaSync();
|
|
588
|
+
init_writeLifecycle();
|
|
589
|
+
init_yjsValues();
|
|
590
|
+
init_uniqueIndex();
|
|
492
591
|
registerFunctionDefault(generateULID, "generate_ulid");
|
|
493
592
|
}
|
|
494
593
|
});
|
|
@@ -1518,6 +1617,45 @@ var DurableObjectEngine = class extends JsonSchemaEngine {
|
|
|
1518
1617
|
init_queryTypes();
|
|
1519
1618
|
init_CursorManager();
|
|
1520
1619
|
init_sql();
|
|
1620
|
+
|
|
1621
|
+
// src/query/recordRow.ts
|
|
1622
|
+
var PROJECTED_JSON_ALIAS_PREFIX = "_json_";
|
|
1623
|
+
function projectedJsonAlias(fieldName) {
|
|
1624
|
+
return `${PROJECTED_JSON_ALIAS_PREFIX}${fieldName}`;
|
|
1625
|
+
}
|
|
1626
|
+
function parseRecordRow(row) {
|
|
1627
|
+
const { _id, _type, _data, ...rest } = row;
|
|
1628
|
+
let parsed = {};
|
|
1629
|
+
if (_data) {
|
|
1630
|
+
try {
|
|
1631
|
+
parsed = JSON.parse(_data);
|
|
1632
|
+
} catch (e) {
|
|
1633
|
+
console.warn("Failed to parse _data:", e);
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
return { id: _id, type: _type, ...parsed, ...decodeProjectedColumns(rest) };
|
|
1637
|
+
}
|
|
1638
|
+
function decodeProjectedColumns(columns) {
|
|
1639
|
+
const decoded = {};
|
|
1640
|
+
for (const [column, value] of Object.entries(columns)) {
|
|
1641
|
+
if (!column.startsWith(PROJECTED_JSON_ALIAS_PREFIX)) {
|
|
1642
|
+
decoded[column] = value;
|
|
1643
|
+
continue;
|
|
1644
|
+
}
|
|
1645
|
+
decoded[column.slice(PROJECTED_JSON_ALIAS_PREFIX.length)] = parseJsonColumn(value);
|
|
1646
|
+
}
|
|
1647
|
+
return decoded;
|
|
1648
|
+
}
|
|
1649
|
+
function parseJsonColumn(value) {
|
|
1650
|
+
if (typeof value !== "string") return value;
|
|
1651
|
+
try {
|
|
1652
|
+
return JSON.parse(value);
|
|
1653
|
+
} catch {
|
|
1654
|
+
return value;
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
// src/query/JsonQueryTranslator.ts
|
|
1521
1659
|
init_patterns();
|
|
1522
1660
|
var SYSTEM_FIELDS = /* @__PURE__ */ new Set(["id", "type"]);
|
|
1523
1661
|
var JsonQueryTranslator = class {
|
|
@@ -1562,7 +1700,8 @@ var JsonQueryTranslator = class {
|
|
|
1562
1700
|
const whereClause = this.translateFilter(filter);
|
|
1563
1701
|
const orderClause = CursorManager.buildOrderClause(
|
|
1564
1702
|
options?.sort,
|
|
1565
|
-
(field) => this.getFieldSql(field)
|
|
1703
|
+
(field) => this.getFieldSql(field),
|
|
1704
|
+
options?.direction || 1
|
|
1566
1705
|
);
|
|
1567
1706
|
const limitClause = this.buildLimitClause(options);
|
|
1568
1707
|
const paginationClause = this.buildPaginationClause(
|
|
@@ -2013,7 +2152,7 @@ var JsonQueryTranslator = class {
|
|
|
2013
2152
|
for (const field of includeFields) {
|
|
2014
2153
|
if (field === "id" || field === "type") continue;
|
|
2015
2154
|
selectParts.push(
|
|
2016
|
-
`
|
|
2155
|
+
`_data -> '$.${field}' AS ${quoteIdentifier(projectedJsonAlias(field))}`
|
|
2017
2156
|
);
|
|
2018
2157
|
}
|
|
2019
2158
|
return selectParts.join(", ");
|
|
@@ -2200,6 +2339,86 @@ var JsonQueryTranslator = class {
|
|
|
2200
2339
|
init_CursorManager();
|
|
2201
2340
|
init_sql();
|
|
2202
2341
|
import { ulid } from "ulid";
|
|
2342
|
+
var AtomicBatchAbort = class extends Error {
|
|
2343
|
+
constructor(failedIndex) {
|
|
2344
|
+
super(`atomic batch aborted at operation ${failedIndex}`);
|
|
2345
|
+
this.failedIndex = failedIndex;
|
|
2346
|
+
this.name = "AtomicBatchAbort";
|
|
2347
|
+
}
|
|
2348
|
+
};
|
|
2349
|
+
function checkReservedFieldName(fieldName) {
|
|
2350
|
+
if (fieldName === "id") return null;
|
|
2351
|
+
if (fieldName === "type") {
|
|
2352
|
+
return `Field 'type' is reserved (maps to internal _type column in queries)`;
|
|
2353
|
+
}
|
|
2354
|
+
if (fieldName.startsWith("_")) {
|
|
2355
|
+
return `Field '${fieldName}' is reserved (fields starting with '_' are internal)`;
|
|
2356
|
+
}
|
|
2357
|
+
return null;
|
|
2358
|
+
}
|
|
2359
|
+
function checkAutoStampsReservedFields(autoStamps) {
|
|
2360
|
+
if (!autoStamps) return null;
|
|
2361
|
+
for (const field of autoStamps.create || []) {
|
|
2362
|
+
const err = checkReservedFieldName(field);
|
|
2363
|
+
if (err) return `autoStamps.create: ${err}`;
|
|
2364
|
+
}
|
|
2365
|
+
for (const field of autoStamps.update || []) {
|
|
2366
|
+
const err = checkReservedFieldName(field);
|
|
2367
|
+
if (err) return `autoStamps.update: ${err}`;
|
|
2368
|
+
}
|
|
2369
|
+
return null;
|
|
2370
|
+
}
|
|
2371
|
+
function applyAutoStamps(data, autoStamps, isNew) {
|
|
2372
|
+
if (!autoStamps || !data) return;
|
|
2373
|
+
const reservedError = checkAutoStampsReservedFields(autoStamps);
|
|
2374
|
+
if (reservedError) {
|
|
2375
|
+
throw new Error(reservedError);
|
|
2376
|
+
}
|
|
2377
|
+
const now = Date.now();
|
|
2378
|
+
if (isNew && autoStamps.create) {
|
|
2379
|
+
for (const field of autoStamps.create) {
|
|
2380
|
+
if (data[field] === void 0 || data[field] === null) {
|
|
2381
|
+
data[field] = now;
|
|
2382
|
+
}
|
|
2383
|
+
}
|
|
2384
|
+
}
|
|
2385
|
+
if (autoStamps.update) {
|
|
2386
|
+
for (const field of autoStamps.update) {
|
|
2387
|
+
if (data[field] === void 0 || data[field] === null) {
|
|
2388
|
+
data[field] = now;
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
function collectAppliedStamps(data, autoStamps, isNew) {
|
|
2394
|
+
if (!autoStamps || !data) return void 0;
|
|
2395
|
+
const stamps = {};
|
|
2396
|
+
const take = (fields) => {
|
|
2397
|
+
if (!fields) return;
|
|
2398
|
+
for (const field of fields) {
|
|
2399
|
+
const value = data[field];
|
|
2400
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
2401
|
+
stamps[field] = value;
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
2404
|
+
};
|
|
2405
|
+
if (isNew) take(autoStamps.create);
|
|
2406
|
+
take(autoStamps.update);
|
|
2407
|
+
return Object.keys(stamps).length > 0 ? stamps : void 0;
|
|
2408
|
+
}
|
|
2409
|
+
function collectAppliedValues(data, fieldNames) {
|
|
2410
|
+
if (!data || !fieldNames || fieldNames.length === 0) return void 0;
|
|
2411
|
+
const out = {};
|
|
2412
|
+
for (const name of fieldNames) {
|
|
2413
|
+
const value = data[name];
|
|
2414
|
+
if (value !== void 0) out[name] = value;
|
|
2415
|
+
}
|
|
2416
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
2417
|
+
}
|
|
2418
|
+
function mergeAppliedFields(stamps, applied) {
|
|
2419
|
+
if (!stamps && !applied) return void 0;
|
|
2420
|
+
return { ...stamps ?? {}, ...applied ?? {} };
|
|
2421
|
+
}
|
|
2203
2422
|
function createDatabaseDO(config = {}) {
|
|
2204
2423
|
const hooks = config.hooks;
|
|
2205
2424
|
return class DocumentDO {
|
|
@@ -2357,6 +2576,16 @@ function createDatabaseDO(config = {}) {
|
|
|
2357
2576
|
hasMore,
|
|
2358
2577
|
isFirstPage
|
|
2359
2578
|
);
|
|
2579
|
+
if (options?.projection && Object.keys(options.projection).length > 0) {
|
|
2580
|
+
const isExclusion = Object.values(options.projection).some(
|
|
2581
|
+
(v) => v === 0
|
|
2582
|
+
);
|
|
2583
|
+
if (isExclusion) {
|
|
2584
|
+
data = data.map(
|
|
2585
|
+
(rec) => this._applyProjection(rec, options.projection)
|
|
2586
|
+
);
|
|
2587
|
+
}
|
|
2588
|
+
}
|
|
2360
2589
|
const response = {
|
|
2361
2590
|
data,
|
|
2362
2591
|
hasMore,
|
|
@@ -2380,7 +2609,15 @@ function createDatabaseDO(config = {}) {
|
|
|
2380
2609
|
/** @internal */
|
|
2381
2610
|
async _handleSave(request, docId) {
|
|
2382
2611
|
const body = await request.json();
|
|
2383
|
-
const {
|
|
2612
|
+
const {
|
|
2613
|
+
modelName,
|
|
2614
|
+
data,
|
|
2615
|
+
stringSets,
|
|
2616
|
+
ifNotExists,
|
|
2617
|
+
condition,
|
|
2618
|
+
upsertOn,
|
|
2619
|
+
autoStamps
|
|
2620
|
+
} = body;
|
|
2384
2621
|
let id = body.id;
|
|
2385
2622
|
if (!modelName || !id && !upsertOn) {
|
|
2386
2623
|
return this._errorResponse("modelName is required, and either id or upsertOn must be provided", 400);
|
|
@@ -2394,6 +2631,12 @@ function createDatabaseDO(config = {}) {
|
|
|
2394
2631
|
return this._errorResponse(reservedError, 400);
|
|
2395
2632
|
}
|
|
2396
2633
|
}
|
|
2634
|
+
{
|
|
2635
|
+
const autoStampsReservedError = checkAutoStampsReservedFields(autoStamps);
|
|
2636
|
+
if (autoStampsReservedError) {
|
|
2637
|
+
return this._errorResponse(autoStampsReservedError, 400);
|
|
2638
|
+
}
|
|
2639
|
+
}
|
|
2397
2640
|
if (upsertOn) {
|
|
2398
2641
|
if (data[upsertOn] === void 0 || data[upsertOn] === null || data[upsertOn] === "") {
|
|
2399
2642
|
return this._errorResponse(
|
|
@@ -2443,8 +2686,10 @@ function createDatabaseDO(config = {}) {
|
|
|
2443
2686
|
409
|
|
2444
2687
|
);
|
|
2445
2688
|
}
|
|
2689
|
+
const isNew = !this._engine.recordExists(modelName, id);
|
|
2690
|
+
applyAutoStamps(data, autoStamps, isNew);
|
|
2691
|
+
let hookAppliedNames;
|
|
2446
2692
|
if (hooks?.beforeSave) {
|
|
2447
|
-
const isNew = !this._engine.recordExists(modelName, id);
|
|
2448
2693
|
const ctx = {
|
|
2449
2694
|
modelName,
|
|
2450
2695
|
docId,
|
|
@@ -2459,6 +2704,7 @@ function createDatabaseDO(config = {}) {
|
|
|
2459
2704
|
if (!result.allow) {
|
|
2460
2705
|
return this._errorResponse(result.reason || "Save denied", 403);
|
|
2461
2706
|
}
|
|
2707
|
+
hookAppliedNames = result.serverAppliedFieldNames;
|
|
2462
2708
|
}
|
|
2463
2709
|
const existingForUnique = this._engine.execSqlSync(
|
|
2464
2710
|
"SELECT _data FROM records WHERE _id = ? AND _type = ?",
|
|
@@ -2488,12 +2734,17 @@ function createDatabaseDO(config = {}) {
|
|
|
2488
2734
|
}
|
|
2489
2735
|
this._engine.trackModelFields(modelName, data);
|
|
2490
2736
|
const response = { success: true, id };
|
|
2737
|
+
const appliedFields = mergeAppliedFields(
|
|
2738
|
+
collectAppliedStamps(data, autoStamps, isNew),
|
|
2739
|
+
collectAppliedValues(data, hookAppliedNames)
|
|
2740
|
+
);
|
|
2741
|
+
if (appliedFields) response.appliedFields = appliedFields;
|
|
2491
2742
|
return Response.json(response);
|
|
2492
2743
|
}
|
|
2493
2744
|
/** @internal — Partial update: merge provided fields into existing record */
|
|
2494
2745
|
async _handlePatch(request, docId) {
|
|
2495
2746
|
const body = await request.json();
|
|
2496
|
-
const { modelName, id, data, stringSets, condition } = body;
|
|
2747
|
+
const { modelName, id, data, stringSets, condition, autoStamps } = body;
|
|
2497
2748
|
if (!modelName || !id || !data) {
|
|
2498
2749
|
return this._errorResponse("modelName, id, and data are required", 400);
|
|
2499
2750
|
}
|
|
@@ -2501,6 +2752,12 @@ function createDatabaseDO(config = {}) {
|
|
|
2501
2752
|
if (reservedError) {
|
|
2502
2753
|
return this._errorResponse(reservedError, 400);
|
|
2503
2754
|
}
|
|
2755
|
+
{
|
|
2756
|
+
const autoStampsReservedError = checkAutoStampsReservedFields(autoStamps);
|
|
2757
|
+
if (autoStampsReservedError) {
|
|
2758
|
+
return this._errorResponse(autoStampsReservedError, 400);
|
|
2759
|
+
}
|
|
2760
|
+
}
|
|
2504
2761
|
if (!this._engine.recordExists(modelName, id)) {
|
|
2505
2762
|
return this._errorResponse(`Record ${modelName}/${id} not found`, 404);
|
|
2506
2763
|
}
|
|
@@ -2510,6 +2767,8 @@ function createDatabaseDO(config = {}) {
|
|
|
2510
2767
|
409
|
|
2511
2768
|
);
|
|
2512
2769
|
}
|
|
2770
|
+
applyAutoStamps(data, autoStamps, false);
|
|
2771
|
+
let hookAppliedNames;
|
|
2513
2772
|
if (hooks?.beforeSave) {
|
|
2514
2773
|
const ctx = {
|
|
2515
2774
|
modelName,
|
|
@@ -2525,6 +2784,7 @@ function createDatabaseDO(config = {}) {
|
|
|
2525
2784
|
if (!result.allow) {
|
|
2526
2785
|
return this._errorResponse(result.reason || "Save denied", 403);
|
|
2527
2786
|
}
|
|
2787
|
+
hookAppliedNames = result.serverAppliedFieldNames;
|
|
2528
2788
|
}
|
|
2529
2789
|
const existing = this._engine.execSqlSync(
|
|
2530
2790
|
"SELECT _data FROM records WHERE _id = ? AND _type = ?",
|
|
@@ -2548,6 +2808,11 @@ function createDatabaseDO(config = {}) {
|
|
|
2548
2808
|
}
|
|
2549
2809
|
this._engine.trackModelFields(modelName, data);
|
|
2550
2810
|
const response = { success: true, id };
|
|
2811
|
+
const appliedFields = mergeAppliedFields(
|
|
2812
|
+
collectAppliedStamps(data, autoStamps, false),
|
|
2813
|
+
collectAppliedValues(data, hookAppliedNames)
|
|
2814
|
+
);
|
|
2815
|
+
if (appliedFields) response.appliedFields = appliedFields;
|
|
2551
2816
|
return Response.json(response);
|
|
2552
2817
|
}
|
|
2553
2818
|
/** @internal */
|
|
@@ -2589,13 +2854,16 @@ function createDatabaseDO(config = {}) {
|
|
|
2589
2854
|
/** @internal — Execute multiple save/patch/delete operations in a single transaction */
|
|
2590
2855
|
async _handleBatch(request, docId) {
|
|
2591
2856
|
const body = await request.json();
|
|
2592
|
-
const { operations } = body;
|
|
2857
|
+
const { operations, atomic = false } = body;
|
|
2593
2858
|
if (!operations || !Array.isArray(operations) || operations.length === 0) {
|
|
2594
2859
|
return this._errorResponse("operations array is required and must not be empty", 400);
|
|
2595
2860
|
}
|
|
2596
2861
|
const hookDenials = /* @__PURE__ */ new Map();
|
|
2597
2862
|
const resolvedBatchIds = /* @__PURE__ */ new Map();
|
|
2598
2863
|
const opErrors = /* @__PURE__ */ new Map();
|
|
2864
|
+
const batchCreatedIds = /* @__PURE__ */ new Set();
|
|
2865
|
+
const batchIsNew = /* @__PURE__ */ new Map();
|
|
2866
|
+
const batchAppliedNames = /* @__PURE__ */ new Map();
|
|
2599
2867
|
for (let i = 0; i < operations.length; i++) {
|
|
2600
2868
|
const op = operations[i];
|
|
2601
2869
|
if (!op.modelName || !op.id && !(op.op === "save" && op.upsertOn) || !op.op) {
|
|
@@ -2647,9 +2915,31 @@ function createDatabaseDO(config = {}) {
|
|
|
2647
2915
|
400
|
|
2648
2916
|
);
|
|
2649
2917
|
}
|
|
2918
|
+
const autoStampsReservedError = checkAutoStampsReservedFields(
|
|
2919
|
+
op.autoStamps
|
|
2920
|
+
);
|
|
2921
|
+
if (autoStampsReservedError) {
|
|
2922
|
+
return this._errorResponse(
|
|
2923
|
+
`Operation ${i}: ${autoStampsReservedError}`,
|
|
2924
|
+
400
|
|
2925
|
+
);
|
|
2926
|
+
}
|
|
2927
|
+
const effectiveId = resolvedBatchIds.get(i) || op.id || "";
|
|
2928
|
+
const isNew = op.op === "save" ? (
|
|
2929
|
+
// Codex P2 #3: also treat the id as not-new if a previous
|
|
2930
|
+
// op in this batch already resolved to a save for the same
|
|
2931
|
+
// model+id. Without this, two saves to the same fresh id
|
|
2932
|
+
// both compute isNew=true against the pre-transaction
|
|
2933
|
+
// database, so a `'create'` autoStamp on the second op
|
|
2934
|
+
// would overwrite the first op's stamp.
|
|
2935
|
+
!batchCreatedIds.has(`${op.modelName}/${effectiveId}`) && !this._engine.recordExists(op.modelName, effectiveId)
|
|
2936
|
+
) : false;
|
|
2937
|
+
if (op.op === "save" && isNew && effectiveId) {
|
|
2938
|
+
batchCreatedIds.add(`${op.modelName}/${effectiveId}`);
|
|
2939
|
+
}
|
|
2940
|
+
batchIsNew.set(i, isNew);
|
|
2941
|
+
applyAutoStamps(op.data, op.autoStamps, isNew);
|
|
2650
2942
|
if (hooks?.beforeSave) {
|
|
2651
|
-
const effectiveId = resolvedBatchIds.get(i) || op.id || "";
|
|
2652
|
-
const isNew = op.op === "save" ? !this._engine.recordExists(op.modelName, effectiveId) : false;
|
|
2653
2943
|
const ctx = {
|
|
2654
2944
|
modelName: op.modelName,
|
|
2655
2945
|
docId,
|
|
@@ -2663,6 +2953,8 @@ function createDatabaseDO(config = {}) {
|
|
|
2663
2953
|
const result = await hooks.beforeSave(ctx);
|
|
2664
2954
|
if (!result.allow) {
|
|
2665
2955
|
hookDenials.set(i, result.reason || "Save denied");
|
|
2956
|
+
} else if (result.serverAppliedFieldNames) {
|
|
2957
|
+
batchAppliedNames.set(i, result.serverAppliedFieldNames);
|
|
2666
2958
|
}
|
|
2667
2959
|
}
|
|
2668
2960
|
} else if (op.op === "delete") {
|
|
@@ -2711,205 +3003,263 @@ function createDatabaseDO(config = {}) {
|
|
|
2711
3003
|
}
|
|
2712
3004
|
const results = [];
|
|
2713
3005
|
const fieldsToTrack = [];
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
if (op.condition && !this._checkCondition(op.modelName, conditionId, op.condition)) {
|
|
2736
|
-
results.push({
|
|
2737
|
-
success: false,
|
|
2738
|
-
id: conditionId,
|
|
2739
|
-
error: `Condition not met for ${op.modelName}/${conditionId}`
|
|
2740
|
-
});
|
|
2741
|
-
continue;
|
|
2742
|
-
}
|
|
2743
|
-
if (op.op === "save") {
|
|
2744
|
-
let opId = resolvedBatchIds.get(i) || op.id;
|
|
2745
|
-
if (op.ifNotExists && this._engine.recordExists(op.modelName, opId)) {
|
|
2746
|
-
results.push({
|
|
2747
|
-
success: false,
|
|
2748
|
-
id: opId,
|
|
2749
|
-
error: `Record ${op.modelName}/${opId} already exists`
|
|
3006
|
+
const pushFailure = (index, failure) => {
|
|
3007
|
+
results.push({
|
|
3008
|
+
success: false,
|
|
3009
|
+
id: failure.id,
|
|
3010
|
+
error: failure.error,
|
|
3011
|
+
status: failure.status,
|
|
3012
|
+
code: failure.code
|
|
3013
|
+
});
|
|
3014
|
+
if (atomic) throw new AtomicBatchAbort(index);
|
|
3015
|
+
};
|
|
3016
|
+
try {
|
|
3017
|
+
this._engine.transactionSync(() => {
|
|
3018
|
+
for (let i = 0; i < operations.length; i++) {
|
|
3019
|
+
const op = operations[i];
|
|
3020
|
+
if (hookDenials.has(i)) {
|
|
3021
|
+
const effectiveId = resolvedBatchIds.get(i) || op.id || "";
|
|
3022
|
+
pushFailure(i, {
|
|
3023
|
+
id: effectiveId,
|
|
3024
|
+
error: hookDenials.get(i),
|
|
3025
|
+
status: 403,
|
|
3026
|
+
code: "HOOK_DENIED"
|
|
2750
3027
|
});
|
|
2751
3028
|
continue;
|
|
2752
3029
|
}
|
|
2753
|
-
if (
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
3030
|
+
if (opErrors.has(i)) {
|
|
3031
|
+
pushFailure(i, {
|
|
3032
|
+
id: op.id || "",
|
|
3033
|
+
error: opErrors.get(i),
|
|
3034
|
+
status: 400,
|
|
3035
|
+
code: "VALIDATION_ERROR"
|
|
3036
|
+
});
|
|
3037
|
+
continue;
|
|
3038
|
+
}
|
|
3039
|
+
const conditionId = resolvedBatchIds.get(i) || op.id;
|
|
3040
|
+
if (op.condition && !this._checkCondition(op.modelName, conditionId, op.condition)) {
|
|
3041
|
+
pushFailure(i, {
|
|
3042
|
+
id: conditionId,
|
|
3043
|
+
error: `Condition not met for ${op.modelName}/${conditionId}`,
|
|
3044
|
+
status: 409,
|
|
3045
|
+
code: "CONDITION_NOT_MET"
|
|
3046
|
+
});
|
|
3047
|
+
continue;
|
|
3048
|
+
}
|
|
3049
|
+
if (op.op === "save") {
|
|
3050
|
+
let opId = resolvedBatchIds.get(i) || op.id;
|
|
3051
|
+
if (op.ifNotExists && this._engine.recordExists(op.modelName, opId)) {
|
|
3052
|
+
pushFailure(i, {
|
|
2771
3053
|
id: opId,
|
|
2772
|
-
error:
|
|
3054
|
+
error: `Record ${op.modelName}/${opId} already exists`,
|
|
3055
|
+
status: 409,
|
|
3056
|
+
code: "ALREADY_EXISTS"
|
|
2773
3057
|
});
|
|
2774
3058
|
continue;
|
|
2775
3059
|
}
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
const dataJson = JSON.stringify(jsonFields);
|
|
2781
|
-
this._engine.execSqlSync(
|
|
2782
|
-
"INSERT INTO records (_id, _type, _data) VALUES (?, ?, ?) ON CONFLICT(_type, _id) DO UPDATE SET _data = json_patch(records._data, excluded._data)",
|
|
2783
|
-
[opId, op.modelName, dataJson]
|
|
2784
|
-
);
|
|
2785
|
-
for (const [fieldName, values] of Object.entries(op.stringSets)) {
|
|
2786
|
-
this._engine.execSqlSync(
|
|
2787
|
-
"DELETE FROM stringset_index WHERE _type = ? AND _record_id = ? AND field = ?",
|
|
2788
|
-
[op.modelName, opId, fieldName]
|
|
3060
|
+
if (op.data) {
|
|
3061
|
+
const existingForUnique = this._engine.execSqlSync(
|
|
3062
|
+
"SELECT _data FROM records WHERE _id = ? AND _type = ?",
|
|
3063
|
+
[opId, op.modelName]
|
|
2789
3064
|
);
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
);
|
|
3065
|
+
let dataForUnique = op.data;
|
|
3066
|
+
if (existingForUnique.length > 0) {
|
|
3067
|
+
const existingData = JSON.parse(existingForUnique[0]._data);
|
|
3068
|
+
dataForUnique = { ...existingData, ...op.data };
|
|
2795
3069
|
}
|
|
2796
|
-
}
|
|
2797
|
-
} else {
|
|
2798
|
-
const { id: _id, ...fieldsForJson } = op.data;
|
|
2799
|
-
const dataJson = JSON.stringify(fieldsForJson);
|
|
2800
|
-
this._engine.execSqlSync(
|
|
2801
|
-
"INSERT INTO records (_id, _type, _data) VALUES (?, ?, ?) ON CONFLICT(_type, _id) DO UPDATE SET _data = json_patch(records._data, excluded._data)",
|
|
2802
|
-
[opId, op.modelName, dataJson]
|
|
2803
|
-
);
|
|
2804
|
-
}
|
|
2805
|
-
if (op.data) fieldsToTrack.push({ modelName: op.modelName, data: op.data });
|
|
2806
|
-
results.push({ success: true, id: opId });
|
|
2807
|
-
} else if (op.op === "patch") {
|
|
2808
|
-
if (!this._engine.recordExists(op.modelName, op.id)) {
|
|
2809
|
-
results.push({
|
|
2810
|
-
success: false,
|
|
2811
|
-
id: op.id,
|
|
2812
|
-
error: `Record ${op.modelName}/${op.id} not found`
|
|
2813
|
-
});
|
|
2814
|
-
continue;
|
|
2815
|
-
}
|
|
2816
|
-
if (op.data) {
|
|
2817
|
-
const existing = this._engine.execSqlSync(
|
|
2818
|
-
"SELECT _data FROM records WHERE _id = ? AND _type = ?",
|
|
2819
|
-
[op.id, op.modelName]
|
|
2820
|
-
);
|
|
2821
|
-
if (existing.length > 0) {
|
|
2822
|
-
const existingData = JSON.parse(existing[0]._data);
|
|
2823
|
-
const mergedData = { ...existingData, ...op.data };
|
|
2824
3070
|
const violation = this._engine.checkUniqueConstraints(
|
|
2825
3071
|
op.modelName,
|
|
2826
|
-
|
|
2827
|
-
|
|
3072
|
+
opId,
|
|
3073
|
+
dataForUnique
|
|
2828
3074
|
);
|
|
2829
3075
|
if (violation) {
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
3076
|
+
pushFailure(i, {
|
|
3077
|
+
id: opId,
|
|
3078
|
+
error: violation,
|
|
3079
|
+
status: 409,
|
|
3080
|
+
code: "UNIQUE_VIOLATION"
|
|
2834
3081
|
});
|
|
2835
3082
|
continue;
|
|
2836
3083
|
}
|
|
2837
3084
|
}
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
3085
|
+
if (op.stringSets && Object.keys(op.stringSets).length > 0) {
|
|
3086
|
+
const { id: _id, ...fieldsForJson } = op.data;
|
|
3087
|
+
const jsonFields = { ...fieldsForJson, ...op.stringSets };
|
|
3088
|
+
const dataJson = JSON.stringify(jsonFields);
|
|
3089
|
+
this._engine.execSqlSync(
|
|
3090
|
+
"INSERT INTO records (_id, _type, _data) VALUES (?, ?, ?) ON CONFLICT(_type, _id) DO UPDATE SET _data = json_patch(records._data, excluded._data)",
|
|
3091
|
+
[opId, op.modelName, dataJson]
|
|
3092
|
+
);
|
|
3093
|
+
for (const [fieldName, values] of Object.entries(op.stringSets)) {
|
|
3094
|
+
this._engine.execSqlSync(
|
|
3095
|
+
"DELETE FROM stringset_index WHERE _type = ? AND _record_id = ? AND field = ?",
|
|
3096
|
+
[op.modelName, opId, fieldName]
|
|
3097
|
+
);
|
|
3098
|
+
for (const value of values) {
|
|
3099
|
+
this._engine.execSqlSync(
|
|
3100
|
+
"INSERT OR IGNORE INTO stringset_index (_record_id, _type, field, value) VALUES (?, ?, ?, ?)",
|
|
3101
|
+
[opId, op.modelName, fieldName, value]
|
|
3102
|
+
);
|
|
3103
|
+
}
|
|
3104
|
+
}
|
|
3105
|
+
} else {
|
|
3106
|
+
const { id: _id, ...fieldsForJson } = op.data;
|
|
3107
|
+
const dataJson = JSON.stringify(fieldsForJson);
|
|
3108
|
+
this._engine.execSqlSync(
|
|
3109
|
+
"INSERT INTO records (_id, _type, _data) VALUES (?, ?, ?) ON CONFLICT(_type, _id) DO UPDATE SET _data = json_patch(records._data, excluded._data)",
|
|
3110
|
+
[opId, op.modelName, dataJson]
|
|
3111
|
+
);
|
|
3112
|
+
}
|
|
2852
3113
|
if (op.data) fieldsToTrack.push({ modelName: op.modelName, data: op.data });
|
|
3114
|
+
const saveResult = { success: true, id: opId };
|
|
3115
|
+
const saveApplied = mergeAppliedFields(
|
|
3116
|
+
collectAppliedStamps(
|
|
3117
|
+
op.data,
|
|
3118
|
+
op.autoStamps,
|
|
3119
|
+
batchIsNew.get(i) ?? false
|
|
3120
|
+
),
|
|
3121
|
+
collectAppliedValues(op.data, batchAppliedNames.get(i))
|
|
3122
|
+
);
|
|
3123
|
+
if (saveApplied) saveResult.appliedFields = saveApplied;
|
|
3124
|
+
results.push(saveResult);
|
|
3125
|
+
} else if (op.op === "patch") {
|
|
3126
|
+
if (!this._engine.recordExists(op.modelName, op.id)) {
|
|
3127
|
+
pushFailure(i, {
|
|
3128
|
+
id: op.id,
|
|
3129
|
+
error: `Record ${op.modelName}/${op.id} not found`,
|
|
3130
|
+
status: 404,
|
|
3131
|
+
code: "NOT_FOUND"
|
|
3132
|
+
});
|
|
3133
|
+
continue;
|
|
3134
|
+
}
|
|
3135
|
+
if (op.data) {
|
|
3136
|
+
const existing = this._engine.execSqlSync(
|
|
3137
|
+
"SELECT _data FROM records WHERE _id = ? AND _type = ?",
|
|
3138
|
+
[op.id, op.modelName]
|
|
3139
|
+
);
|
|
3140
|
+
if (existing.length > 0) {
|
|
3141
|
+
const existingData = JSON.parse(existing[0]._data);
|
|
3142
|
+
const mergedData = { ...existingData, ...op.data };
|
|
3143
|
+
const violation = this._engine.checkUniqueConstraints(
|
|
3144
|
+
op.modelName,
|
|
3145
|
+
op.id,
|
|
3146
|
+
mergedData
|
|
3147
|
+
);
|
|
3148
|
+
if (violation) {
|
|
3149
|
+
pushFailure(i, {
|
|
3150
|
+
id: op.id,
|
|
3151
|
+
error: violation,
|
|
3152
|
+
status: 409,
|
|
3153
|
+
code: "UNIQUE_VIOLATION"
|
|
3154
|
+
});
|
|
3155
|
+
continue;
|
|
3156
|
+
}
|
|
3157
|
+
}
|
|
3158
|
+
}
|
|
3159
|
+
const found = this._engine.patchRecordRaw(
|
|
3160
|
+
op.modelName,
|
|
3161
|
+
op.id,
|
|
3162
|
+
op.data,
|
|
3163
|
+
op.stringSets
|
|
3164
|
+
);
|
|
3165
|
+
if (!found) {
|
|
3166
|
+
pushFailure(i, {
|
|
3167
|
+
id: op.id,
|
|
3168
|
+
error: `Record ${op.modelName}/${op.id} not found`,
|
|
3169
|
+
status: 404,
|
|
3170
|
+
code: "NOT_FOUND"
|
|
3171
|
+
});
|
|
3172
|
+
} else {
|
|
3173
|
+
if (op.data) fieldsToTrack.push({ modelName: op.modelName, data: op.data });
|
|
3174
|
+
const patchResult = {
|
|
3175
|
+
success: true,
|
|
3176
|
+
id: op.id
|
|
3177
|
+
};
|
|
3178
|
+
const patchApplied = mergeAppliedFields(
|
|
3179
|
+
collectAppliedStamps(op.data, op.autoStamps, false),
|
|
3180
|
+
collectAppliedValues(op.data, batchAppliedNames.get(i))
|
|
3181
|
+
);
|
|
3182
|
+
if (patchApplied) patchResult.appliedFields = patchApplied;
|
|
3183
|
+
results.push(patchResult);
|
|
3184
|
+
}
|
|
3185
|
+
} else if (op.op === "delete") {
|
|
3186
|
+
this._engine.execSqlSync(
|
|
3187
|
+
"DELETE FROM records WHERE _id = ? AND _type = ?",
|
|
3188
|
+
[op.id, op.modelName]
|
|
3189
|
+
);
|
|
3190
|
+
this._engine.execSqlSync(
|
|
3191
|
+
"DELETE FROM stringset_index WHERE _record_id = ? AND _type = ?",
|
|
3192
|
+
[op.id, op.modelName]
|
|
3193
|
+
);
|
|
3194
|
+
results.push({ success: true, id: op.id });
|
|
3195
|
+
} else if (op.op === "increment") {
|
|
3196
|
+
const newValues = this._engine.incrementFieldsRaw(
|
|
3197
|
+
op.modelName,
|
|
3198
|
+
op.id,
|
|
3199
|
+
op.fields
|
|
3200
|
+
);
|
|
3201
|
+
if (!newValues) {
|
|
3202
|
+
pushFailure(i, {
|
|
3203
|
+
id: op.id,
|
|
3204
|
+
error: `Record ${op.modelName}/${op.id} not found`,
|
|
3205
|
+
status: 404,
|
|
3206
|
+
code: "NOT_FOUND"
|
|
3207
|
+
});
|
|
3208
|
+
} else {
|
|
3209
|
+
results.push({ success: true, id: op.id, values: newValues });
|
|
3210
|
+
}
|
|
3211
|
+
} else if (op.op === "addToSet") {
|
|
3212
|
+
if (!this._engine.recordExists(op.modelName, op.id)) {
|
|
3213
|
+
pushFailure(i, {
|
|
3214
|
+
id: op.id,
|
|
3215
|
+
error: `Record ${op.modelName}/${op.id} not found`,
|
|
3216
|
+
status: 404,
|
|
3217
|
+
code: "NOT_FOUND"
|
|
3218
|
+
});
|
|
3219
|
+
continue;
|
|
3220
|
+
}
|
|
3221
|
+
this._engine.addToStringSetsRaw(
|
|
3222
|
+
op.modelName,
|
|
3223
|
+
op.id,
|
|
3224
|
+
op.stringSets
|
|
3225
|
+
);
|
|
3226
|
+
results.push({ success: true, id: op.id });
|
|
3227
|
+
} else if (op.op === "removeFromSet") {
|
|
3228
|
+
if (!this._engine.recordExists(op.modelName, op.id)) {
|
|
3229
|
+
pushFailure(i, {
|
|
3230
|
+
id: op.id,
|
|
3231
|
+
error: `Record ${op.modelName}/${op.id} not found`,
|
|
3232
|
+
status: 404,
|
|
3233
|
+
code: "NOT_FOUND"
|
|
3234
|
+
});
|
|
3235
|
+
continue;
|
|
3236
|
+
}
|
|
3237
|
+
this._engine.removeFromStringSetsRaw(
|
|
3238
|
+
op.modelName,
|
|
3239
|
+
op.id,
|
|
3240
|
+
op.stringSets
|
|
3241
|
+
);
|
|
2853
3242
|
results.push({ success: true, id: op.id });
|
|
2854
3243
|
}
|
|
2855
|
-
} else if (op.op === "delete") {
|
|
2856
|
-
this._engine.execSqlSync(
|
|
2857
|
-
"DELETE FROM records WHERE _id = ? AND _type = ?",
|
|
2858
|
-
[op.id, op.modelName]
|
|
2859
|
-
);
|
|
2860
|
-
this._engine.execSqlSync(
|
|
2861
|
-
"DELETE FROM stringset_index WHERE _record_id = ? AND _type = ?",
|
|
2862
|
-
[op.id, op.modelName]
|
|
2863
|
-
);
|
|
2864
|
-
results.push({ success: true, id: op.id });
|
|
2865
|
-
} else if (op.op === "increment") {
|
|
2866
|
-
const newValues = this._engine.incrementFieldsRaw(
|
|
2867
|
-
op.modelName,
|
|
2868
|
-
op.id,
|
|
2869
|
-
op.fields
|
|
2870
|
-
);
|
|
2871
|
-
if (!newValues) {
|
|
2872
|
-
results.push({
|
|
2873
|
-
success: false,
|
|
2874
|
-
id: op.id,
|
|
2875
|
-
error: `Record ${op.modelName}/${op.id} not found`
|
|
2876
|
-
});
|
|
2877
|
-
} else {
|
|
2878
|
-
results.push({ success: true, id: op.id, values: newValues });
|
|
2879
|
-
}
|
|
2880
|
-
} else if (op.op === "addToSet") {
|
|
2881
|
-
if (!this._engine.recordExists(op.modelName, op.id)) {
|
|
2882
|
-
results.push({
|
|
2883
|
-
success: false,
|
|
2884
|
-
id: op.id,
|
|
2885
|
-
error: `Record ${op.modelName}/${op.id} not found`
|
|
2886
|
-
});
|
|
2887
|
-
continue;
|
|
2888
|
-
}
|
|
2889
|
-
this._engine.addToStringSetsRaw(
|
|
2890
|
-
op.modelName,
|
|
2891
|
-
op.id,
|
|
2892
|
-
op.stringSets
|
|
2893
|
-
);
|
|
2894
|
-
results.push({ success: true, id: op.id });
|
|
2895
|
-
} else if (op.op === "removeFromSet") {
|
|
2896
|
-
if (!this._engine.recordExists(op.modelName, op.id)) {
|
|
2897
|
-
results.push({
|
|
2898
|
-
success: false,
|
|
2899
|
-
id: op.id,
|
|
2900
|
-
error: `Record ${op.modelName}/${op.id} not found`
|
|
2901
|
-
});
|
|
2902
|
-
continue;
|
|
2903
|
-
}
|
|
2904
|
-
this._engine.removeFromStringSetsRaw(
|
|
2905
|
-
op.modelName,
|
|
2906
|
-
op.id,
|
|
2907
|
-
op.stringSets
|
|
2908
|
-
);
|
|
2909
|
-
results.push({ success: true, id: op.id });
|
|
2910
3244
|
}
|
|
3245
|
+
});
|
|
3246
|
+
} catch (err) {
|
|
3247
|
+
if (err instanceof AtomicBatchAbort) {
|
|
3248
|
+
const failed = results[err.failedIndex];
|
|
3249
|
+
const failedOp = operations[err.failedIndex];
|
|
3250
|
+
console.warn(
|
|
3251
|
+
`[js-bao batch] atomic rollback: operation ${err.failedIndex} (${failedOp?.op}) failed \u2014 ${failed?.error ?? "unknown error"}`
|
|
3252
|
+
);
|
|
3253
|
+
const errorBody = {
|
|
3254
|
+
error: `Operation ${err.failedIndex} (${failedOp?.op}) failed: ${failed?.error ?? "unknown error"}`,
|
|
3255
|
+
failedIndex: err.failedIndex,
|
|
3256
|
+
op: failedOp?.op,
|
|
3257
|
+
code: failed?.code
|
|
3258
|
+
};
|
|
3259
|
+
return Response.json(errorBody, { status: failed?.status ?? 409 });
|
|
2911
3260
|
}
|
|
2912
|
-
|
|
3261
|
+
throw err;
|
|
3262
|
+
}
|
|
2913
3263
|
for (const entry of fieldsToTrack) {
|
|
2914
3264
|
this._engine.trackModelFields(entry.modelName, entry.data);
|
|
2915
3265
|
}
|
|
@@ -3279,6 +3629,18 @@ function createDatabaseDO(config = {}) {
|
|
|
3279
3629
|
}
|
|
3280
3630
|
return facetResult;
|
|
3281
3631
|
}
|
|
3632
|
+
if (aggOptions.groupBy.length === 0) {
|
|
3633
|
+
const row = results[0];
|
|
3634
|
+
const flat = {};
|
|
3635
|
+
for (const op of aggOptions.operations) {
|
|
3636
|
+
if (op.type === "count") {
|
|
3637
|
+
flat.count = row.count;
|
|
3638
|
+
} else {
|
|
3639
|
+
flat[`${op.type}_${op.field}`] = row[`${op.type}_${op.field}`];
|
|
3640
|
+
}
|
|
3641
|
+
}
|
|
3642
|
+
return flat;
|
|
3643
|
+
}
|
|
3282
3644
|
const nestedResult = {};
|
|
3283
3645
|
const aliasLookup = /* @__PURE__ */ new Map();
|
|
3284
3646
|
for (const entry of aliasMap) {
|
|
@@ -3606,16 +3968,7 @@ function createDatabaseDO(config = {}) {
|
|
|
3606
3968
|
}
|
|
3607
3969
|
/** @internal */
|
|
3608
3970
|
_parseRow(row) {
|
|
3609
|
-
|
|
3610
|
-
let parsed = {};
|
|
3611
|
-
if (_data) {
|
|
3612
|
-
try {
|
|
3613
|
-
parsed = JSON.parse(_data);
|
|
3614
|
-
} catch (e) {
|
|
3615
|
-
console.warn("Failed to parse _data:", e);
|
|
3616
|
-
}
|
|
3617
|
-
}
|
|
3618
|
-
return { id: _id, type: _type, ...parsed, ...rest };
|
|
3971
|
+
return parseRecordRow(row);
|
|
3619
3972
|
}
|
|
3620
3973
|
// ─── Include (Related Data Loading) ──────────────────────────────
|
|
3621
3974
|
/** @internal — Validate an IncludeSpec, returning an error message or null */
|
|
@@ -3745,8 +4098,9 @@ function createDatabaseDO(config = {}) {
|
|
|
3745
4098
|
spec.filter,
|
|
3746
4099
|
{ projection: spec.projection }
|
|
3747
4100
|
);
|
|
4101
|
+
const relatedProjected = spec.projection ? related.map((r) => this._applyProjection(r, spec.projection)) : related;
|
|
3748
4102
|
const lookupMap = /* @__PURE__ */ new Map();
|
|
3749
|
-
for (const r of
|
|
4103
|
+
for (const r of relatedProjected) {
|
|
3750
4104
|
lookupMap.set(r.id, r);
|
|
3751
4105
|
}
|
|
3752
4106
|
for (const rec of records) {
|
|
@@ -3779,8 +4133,9 @@ function createDatabaseDO(config = {}) {
|
|
|
3779
4133
|
spec.filter,
|
|
3780
4134
|
{ projection: spec.projection }
|
|
3781
4135
|
);
|
|
4136
|
+
const targetsProjected = spec.projection ? targets.map((t) => this._applyProjection(t, spec.projection)) : targets;
|
|
3782
4137
|
const targetLookup = /* @__PURE__ */ new Map();
|
|
3783
|
-
for (const t of
|
|
4138
|
+
for (const t of targetsProjected) {
|
|
3784
4139
|
targetLookup.set(t.id, t);
|
|
3785
4140
|
}
|
|
3786
4141
|
for (const rec of records) {
|
|
@@ -3924,6 +4279,14 @@ function createDatabaseDO(config = {}) {
|
|
|
3924
4279
|
return allResults;
|
|
3925
4280
|
}
|
|
3926
4281
|
/** @internal — Apply projection to a single record */
|
|
4282
|
+
// Apply a projection to a single record. Assumes the projection has
|
|
4283
|
+
// already been validated upstream (JsonQueryTranslator.validateProjection /
|
|
4284
|
+
// buildSelectClause reject mixed inclusion+exclusion): `isInclusion` treats
|
|
4285
|
+
// "any value === 1" as inclusion mode, so an unvalidated mixed projection
|
|
4286
|
+
// would silently ignore the excludes. `id` and `type` are system-identity
|
|
4287
|
+
// fields and are always kept in BOTH modes (a record without its `id` is
|
|
4288
|
+
// unusable downstream). `_meta` is NOT forced — an explicit `{ _meta: 0 }`
|
|
4289
|
+
// is honored.
|
|
3927
4290
|
_applyProjection(record, projection) {
|
|
3928
4291
|
const fields = Object.entries(projection);
|
|
3929
4292
|
if (fields.length === 0) return record;
|
|
@@ -3942,7 +4305,7 @@ function createDatabaseDO(config = {}) {
|
|
|
3942
4305
|
} else {
|
|
3943
4306
|
const result = { ...record };
|
|
3944
4307
|
for (const [f, v] of fields) {
|
|
3945
|
-
if (v === 0) delete result[f];
|
|
4308
|
+
if (v === 0 && f !== "id" && f !== "type") delete result[f];
|
|
3946
4309
|
}
|
|
3947
4310
|
return result;
|
|
3948
4311
|
}
|
|
@@ -4010,7 +4373,8 @@ async function handleRequest(request, env) {
|
|
|
4010
4373
|
}
|
|
4011
4374
|
|
|
4012
4375
|
// src/utils/yDocSchema.ts
|
|
4013
|
-
|
|
4376
|
+
init_yjsValues();
|
|
4377
|
+
import * as Y2 from "yjs";
|
|
4014
4378
|
function discoverSchema(yDoc) {
|
|
4015
4379
|
const models = {};
|
|
4016
4380
|
const metaNames = /* @__PURE__ */ new Set();
|
|
@@ -4044,7 +4408,7 @@ function discoverModelNames(yDoc) {
|
|
|
4044
4408
|
function materializeMap(yDoc, key) {
|
|
4045
4409
|
try {
|
|
4046
4410
|
const map = yDoc.getMap(key);
|
|
4047
|
-
return map instanceof
|
|
4411
|
+
return map instanceof Y2.Map ? map : null;
|
|
4048
4412
|
} catch {
|
|
4049
4413
|
return null;
|
|
4050
4414
|
}
|
|
@@ -4054,11 +4418,11 @@ function readModelMeta(metaMap) {
|
|
|
4054
4418
|
let constraints;
|
|
4055
4419
|
let relationships;
|
|
4056
4420
|
for (const [key, value] of metaMap.entries()) {
|
|
4057
|
-
if (key === "_constraints" && value instanceof
|
|
4421
|
+
if (key === "_constraints" && value instanceof Y2.Map) {
|
|
4058
4422
|
constraints = readConstraints(value);
|
|
4059
|
-
} else if (key === "_relationships" && value instanceof
|
|
4423
|
+
} else if (key === "_relationships" && value instanceof Y2.Map) {
|
|
4060
4424
|
relationships = readRelationships(value);
|
|
4061
|
-
} else if (value instanceof
|
|
4425
|
+
} else if (value instanceof Y2.Map) {
|
|
4062
4426
|
fields[key] = readFieldMeta(value);
|
|
4063
4427
|
}
|
|
4064
4428
|
}
|
|
@@ -4077,6 +4441,10 @@ function readFieldMeta(fieldMap) {
|
|
|
4077
4441
|
if (fieldMap.get("unique") === true) field.unique = true;
|
|
4078
4442
|
if (fieldMap.get("required") === true) field.required = true;
|
|
4079
4443
|
if (fieldMap.get("autoAssign") === true) field.autoAssign = true;
|
|
4444
|
+
const autoStamp = fieldMap.get("autoStamp");
|
|
4445
|
+
if (typeof autoStamp === "string" && (autoStamp === "create" || autoStamp === "update" || autoStamp === "both")) {
|
|
4446
|
+
field.autoStamp = autoStamp;
|
|
4447
|
+
}
|
|
4080
4448
|
const def = fieldMap.get("default");
|
|
4081
4449
|
if (def !== void 0) field.default = def;
|
|
4082
4450
|
const maxLength = fieldMap.get("maxLength");
|
|
@@ -4089,7 +4457,7 @@ function inferModelFromData(dataMap) {
|
|
|
4089
4457
|
const fields = {};
|
|
4090
4458
|
let sampled = 0;
|
|
4091
4459
|
for (const [_recordId, recordValue] of dataMap.entries()) {
|
|
4092
|
-
if (!(recordValue instanceof
|
|
4460
|
+
if (!(recordValue instanceof Y2.Map)) continue;
|
|
4093
4461
|
if (++sampled > 5) break;
|
|
4094
4462
|
for (const [fieldName, value] of recordValue.entries()) {
|
|
4095
4463
|
if (fieldName.startsWith("_")) continue;
|
|
@@ -4102,7 +4470,7 @@ function inferModelFromData(dataMap) {
|
|
|
4102
4470
|
return { fields };
|
|
4103
4471
|
}
|
|
4104
4472
|
function inferTypeFromValue(value) {
|
|
4105
|
-
if (value
|
|
4473
|
+
if (isStringSetYMap(value)) return "stringset";
|
|
4106
4474
|
switch (typeof value) {
|
|
4107
4475
|
case "string":
|
|
4108
4476
|
return "string";
|
|
@@ -4117,7 +4485,7 @@ function inferTypeFromValue(value) {
|
|
|
4117
4485
|
function readConstraints(constraintsMap) {
|
|
4118
4486
|
const out = {};
|
|
4119
4487
|
for (const [name, value] of constraintsMap.entries()) {
|
|
4120
|
-
if (!(value instanceof
|
|
4488
|
+
if (!(value instanceof Y2.Map)) continue;
|
|
4121
4489
|
let fields = [];
|
|
4122
4490
|
const rawFields = value.get("fields");
|
|
4123
4491
|
if (typeof rawFields === "string") {
|
|
@@ -4136,7 +4504,7 @@ function readConstraints(constraintsMap) {
|
|
|
4136
4504
|
function readRelationships(relsMap) {
|
|
4137
4505
|
const out = {};
|
|
4138
4506
|
for (const [name, value] of relsMap.entries()) {
|
|
4139
|
-
if (!(value instanceof
|
|
4507
|
+
if (!(value instanceof Y2.Map)) continue;
|
|
4140
4508
|
const rel = {};
|
|
4141
4509
|
for (const [k, v] of value.entries()) {
|
|
4142
4510
|
rel[k] = v;
|
|
@@ -4147,6 +4515,7 @@ function readRelationships(relsMap) {
|
|
|
4147
4515
|
}
|
|
4148
4516
|
var CAMEL_TO_SNAKE = {
|
|
4149
4517
|
autoAssign: "auto_assign",
|
|
4518
|
+
autoStamp: "auto_stamp",
|
|
4150
4519
|
maxLength: "max_length",
|
|
4151
4520
|
maxCount: "max_count",
|
|
4152
4521
|
relatedIdField: "related_id_field",
|
|
@@ -4177,6 +4546,7 @@ function schemaToToml(schema) {
|
|
|
4177
4546
|
lines.push(`[models.${modelName}.fields.${fieldName}]`);
|
|
4178
4547
|
lines.push(`type = ${tomlValue(field.type)}`);
|
|
4179
4548
|
if (field.autoAssign) lines.push("auto_assign = true");
|
|
4549
|
+
if (field.autoStamp) lines.push(`auto_stamp = ${tomlValue(field.autoStamp)}`);
|
|
4180
4550
|
if (field.indexed) lines.push("indexed = true");
|
|
4181
4551
|
if (field.unique) lines.push("unique = true");
|
|
4182
4552
|
if (field.required) lines.push("required = true");
|
|
@@ -4213,6 +4583,7 @@ import { parse as parseToml } from "smol-toml";
|
|
|
4213
4583
|
// src/models/schema.ts
|
|
4214
4584
|
init_BaseModel();
|
|
4215
4585
|
init_ModelRegistry();
|
|
4586
|
+
init_uniqueIndex();
|
|
4216
4587
|
function defineModelSchema(input) {
|
|
4217
4588
|
const { name, fields } = input;
|
|
4218
4589
|
const options = {
|
|
@@ -4255,7 +4626,7 @@ function resolveUniqueConstraints(modelName, fields, customConstraints) {
|
|
|
4255
4626
|
for (const [fieldName, options] of fields.entries()) {
|
|
4256
4627
|
if (options.unique) {
|
|
4257
4628
|
resolved.push({
|
|
4258
|
-
name:
|
|
4629
|
+
name: fieldUniqueConstraintName(modelName, fieldName),
|
|
4259
4630
|
fields: [fieldName]
|
|
4260
4631
|
});
|
|
4261
4632
|
}
|
|
@@ -4300,10 +4671,13 @@ var KNOWN_FIELD_KEYS = /* @__PURE__ */ new Set([
|
|
|
4300
4671
|
"unique",
|
|
4301
4672
|
"required",
|
|
4302
4673
|
"auto_assign",
|
|
4674
|
+
"auto_stamp",
|
|
4303
4675
|
"max_length",
|
|
4304
4676
|
"max_count",
|
|
4305
|
-
"default"
|
|
4677
|
+
"default",
|
|
4678
|
+
"enum"
|
|
4306
4679
|
]);
|
|
4680
|
+
var VALID_AUTO_STAMP_VALUES = /* @__PURE__ */ new Set(["create", "update", "both"]);
|
|
4307
4681
|
var KNOWN_MODEL_KEYS = /* @__PURE__ */ new Set([
|
|
4308
4682
|
"fields",
|
|
4309
4683
|
"relationships",
|
|
@@ -4345,9 +4719,37 @@ function parseFieldOptions(raw, context, strict) {
|
|
|
4345
4719
|
if (raw.unique === true) opts.unique = true;
|
|
4346
4720
|
if (raw.required === true) opts.required = true;
|
|
4347
4721
|
if (raw.auto_assign === true) opts.autoAssign = true;
|
|
4722
|
+
if (raw.auto_stamp !== void 0) {
|
|
4723
|
+
if (typeof raw.auto_stamp !== "string" || !VALID_AUTO_STAMP_VALUES.has(raw.auto_stamp)) {
|
|
4724
|
+
throw new Error(
|
|
4725
|
+
`${context}: invalid auto_stamp value "${raw.auto_stamp}". Must be one of: ${[
|
|
4726
|
+
...VALID_AUTO_STAMP_VALUES
|
|
4727
|
+
].join(", ")}`
|
|
4728
|
+
);
|
|
4729
|
+
}
|
|
4730
|
+
opts.autoStamp = raw.auto_stamp;
|
|
4731
|
+
}
|
|
4348
4732
|
if (raw.max_length !== void 0) opts.maxLength = raw.max_length;
|
|
4349
4733
|
if (raw.max_count !== void 0) opts.maxCount = raw.max_count;
|
|
4350
4734
|
if (raw.default !== void 0) opts.default = raw.default;
|
|
4735
|
+
if (raw.enum !== void 0) {
|
|
4736
|
+
if (raw.type !== "string") {
|
|
4737
|
+
throw new Error(
|
|
4738
|
+
`${context}: \`enum\` is only valid on a "string" field, not "${raw.type}".`
|
|
4739
|
+
);
|
|
4740
|
+
}
|
|
4741
|
+
if (!Array.isArray(raw.enum) || raw.enum.length === 0) {
|
|
4742
|
+
throw new Error(
|
|
4743
|
+
`${context}: \`enum\` must be a non-empty array of strings.`
|
|
4744
|
+
);
|
|
4745
|
+
}
|
|
4746
|
+
if (!raw.enum.every((v) => typeof v === "string")) {
|
|
4747
|
+
throw new Error(
|
|
4748
|
+
`${context}: \`enum\` values must all be strings.`
|
|
4749
|
+
);
|
|
4750
|
+
}
|
|
4751
|
+
opts.enum = [...raw.enum];
|
|
4752
|
+
}
|
|
4351
4753
|
return opts;
|
|
4352
4754
|
}
|
|
4353
4755
|
function requireField(raw, field, context) {
|
|
@@ -4471,18 +4873,26 @@ function loadSchemaFromTomlString(tomlString, options = {}) {
|
|
|
4471
4873
|
|
|
4472
4874
|
// src/cloudflare-do.ts
|
|
4473
4875
|
init_metaSync();
|
|
4876
|
+
init_sql();
|
|
4474
4877
|
export {
|
|
4475
4878
|
DurableObjectEngine,
|
|
4476
4879
|
JsonQueryTranslator,
|
|
4477
4880
|
JsonSchemaDDL,
|
|
4881
|
+
PROJECTED_JSON_ALIAS_PREFIX,
|
|
4882
|
+
buildSafeAlias,
|
|
4883
|
+
checkAutoStampsReservedFields,
|
|
4884
|
+
checkReservedFieldName,
|
|
4478
4885
|
clearMetaSyncCache,
|
|
4479
4886
|
createDatabaseDO,
|
|
4480
4887
|
createDocumentDO,
|
|
4888
|
+
deterministicIdentifierHash,
|
|
4481
4889
|
discoverModelNames,
|
|
4482
4890
|
discoverSchema,
|
|
4483
4891
|
handleRequest,
|
|
4484
4892
|
inferFieldType,
|
|
4485
4893
|
loadSchemaFromTomlString,
|
|
4894
|
+
parseRecordRow,
|
|
4895
|
+
projectedJsonAlias,
|
|
4486
4896
|
registerFunctionDefault,
|
|
4487
4897
|
schemaToToml,
|
|
4488
4898
|
syncInferredMeta,
|