js-bao 0.5.0 → 0.5.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/dist/browser.cjs +205 -40
- package/dist/browser.d.cts +41 -0
- package/dist/browser.d.ts +41 -0
- package/dist/browser.js +202 -38
- package/dist/client.cjs +15 -4
- package/dist/client.d.cts +78 -0
- package/dist/client.d.ts +78 -0
- package/dist/client.js +12 -2
- package/dist/cloudflare-do.cjs +191 -26
- package/dist/cloudflare-do.d.cts +127 -1
- package/dist/cloudflare-do.d.ts +127 -1
- package/dist/cloudflare-do.js +188 -24
- package/dist/cloudflare.cjs +78 -21
- package/dist/cloudflare.d.cts +84 -0
- package/dist/cloudflare.d.ts +84 -0
- package/dist/cloudflare.js +75 -19
- package/dist/codegen-v2.cjs +67 -7
- package/dist/codegen.cjs +6 -1
- package/dist/index.cjs +209 -44
- package/dist/index.d.cts +41 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.js +206 -42
- package/dist/node.cjs +209 -44
- package/dist/node.d.cts +41 -0
- package/dist/node.d.ts +41 -0
- package/dist/node.js +206 -42
- package/package.json +1 -1
package/dist/cloudflare-do.cjs
CHANGED
|
@@ -44,6 +44,21 @@ function assertValidIdentifier(name, context) {
|
|
|
44
44
|
function quoteIdentifier(name) {
|
|
45
45
|
return `"${name.replace(/"/g, '""')}"`;
|
|
46
46
|
}
|
|
47
|
+
function deterministicIdentifierHash(value) {
|
|
48
|
+
let hash = 0;
|
|
49
|
+
for (let i = 0; i < value.length; i++) {
|
|
50
|
+
hash = (hash << 5) - hash + value.charCodeAt(i);
|
|
51
|
+
hash |= 0;
|
|
52
|
+
}
|
|
53
|
+
const positiveHash = hash === 0 ? 0 : Math.abs(hash);
|
|
54
|
+
return positiveHash.toString(36);
|
|
55
|
+
}
|
|
56
|
+
function buildSafeAlias(prefix, rawDescriptor) {
|
|
57
|
+
const suffix = deterministicIdentifierHash(rawDescriptor);
|
|
58
|
+
const candidate = `${prefix}_${suffix}`;
|
|
59
|
+
assertValidIdentifier(candidate, "Alias generation");
|
|
60
|
+
return candidate;
|
|
61
|
+
}
|
|
47
62
|
var IDENTIFIER_PATTERN;
|
|
48
63
|
var init_sql = __esm({
|
|
49
64
|
"src/utils/sql.ts"() {
|
|
@@ -339,6 +354,22 @@ var init_patterns = __esm({
|
|
|
339
354
|
}
|
|
340
355
|
});
|
|
341
356
|
|
|
357
|
+
// src/utils/yjsValues.ts
|
|
358
|
+
function isStringSetYMap(value) {
|
|
359
|
+
if (!(value instanceof Y.Map)) return false;
|
|
360
|
+
for (const v of value.values()) {
|
|
361
|
+
if (v !== true) return false;
|
|
362
|
+
}
|
|
363
|
+
return true;
|
|
364
|
+
}
|
|
365
|
+
var Y;
|
|
366
|
+
var init_yjsValues = __esm({
|
|
367
|
+
"src/utils/yjsValues.ts"() {
|
|
368
|
+
"use strict";
|
|
369
|
+
Y = __toESM(require("yjs"), 1);
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
|
|
342
373
|
// src/models/StringSet.ts
|
|
343
374
|
var init_StringSet = __esm({
|
|
344
375
|
"src/models/StringSet.ts"() {
|
|
@@ -366,7 +397,7 @@ var init_DocumentQueryTranslator = __esm({
|
|
|
366
397
|
|
|
367
398
|
// src/models/metaSync.ts
|
|
368
399
|
function inferFieldType(value) {
|
|
369
|
-
if (value
|
|
400
|
+
if (isStringSetYMap(value)) return "stringset";
|
|
370
401
|
switch (typeof value) {
|
|
371
402
|
case "string":
|
|
372
403
|
return "string";
|
|
@@ -411,7 +442,7 @@ function syncModelMeta(yDoc, modelName, schema) {
|
|
|
411
442
|
if (compoundConstraints.length > 0) {
|
|
412
443
|
let constraints = meta.get("_constraints");
|
|
413
444
|
if (!constraints) {
|
|
414
|
-
constraints = new
|
|
445
|
+
constraints = new Y3.Map();
|
|
415
446
|
meta.set("_constraints", constraints);
|
|
416
447
|
}
|
|
417
448
|
for (const constraint of compoundConstraints) {
|
|
@@ -422,7 +453,7 @@ function syncModelMeta(yDoc, modelName, schema) {
|
|
|
422
453
|
if (relationships && Object.keys(relationships).length > 0) {
|
|
423
454
|
let rels = meta.get("_relationships");
|
|
424
455
|
if (!rels) {
|
|
425
|
-
rels = new
|
|
456
|
+
rels = new Y3.Map();
|
|
426
457
|
meta.set("_relationships", rels);
|
|
427
458
|
}
|
|
428
459
|
for (const [relName, relConfig] of Object.entries(relationships)) {
|
|
@@ -434,7 +465,7 @@ function syncModelMeta(yDoc, modelName, schema) {
|
|
|
434
465
|
function syncFieldMeta(metaMap, fieldName, fieldOpts) {
|
|
435
466
|
let fieldMeta = metaMap.get(fieldName);
|
|
436
467
|
if (!fieldMeta) {
|
|
437
|
-
fieldMeta = new
|
|
468
|
+
fieldMeta = new Y3.Map();
|
|
438
469
|
metaMap.set(fieldName, fieldMeta);
|
|
439
470
|
}
|
|
440
471
|
setIfChanged(fieldMeta, "type", fieldOpts.type);
|
|
@@ -442,6 +473,7 @@ function syncFieldMeta(metaMap, fieldName, fieldOpts) {
|
|
|
442
473
|
if (fieldOpts.unique) setIfChanged(fieldMeta, "unique", true);
|
|
443
474
|
if (fieldOpts.required) setIfChanged(fieldMeta, "required", true);
|
|
444
475
|
if (fieldOpts.autoAssign) setIfChanged(fieldMeta, "autoAssign", true);
|
|
476
|
+
if (fieldOpts.autoStamp) setIfChanged(fieldMeta, "autoStamp", fieldOpts.autoStamp);
|
|
445
477
|
if (fieldOpts.maxLength !== void 0) setIfChanged(fieldMeta, "maxLength", fieldOpts.maxLength);
|
|
446
478
|
if (fieldOpts.maxCount !== void 0) setIfChanged(fieldMeta, "maxCount", fieldOpts.maxCount);
|
|
447
479
|
const encoded = encodeDefault(fieldOpts.default);
|
|
@@ -450,7 +482,7 @@ function syncFieldMeta(metaMap, fieldName, fieldOpts) {
|
|
|
450
482
|
function syncConstraintMeta(constraintsMap, constraint) {
|
|
451
483
|
let cMeta = constraintsMap.get(constraint.name);
|
|
452
484
|
if (!cMeta) {
|
|
453
|
-
cMeta = new
|
|
485
|
+
cMeta = new Y3.Map();
|
|
454
486
|
constraintsMap.set(constraint.name, cMeta);
|
|
455
487
|
}
|
|
456
488
|
setIfChanged(cMeta, "type", "unique");
|
|
@@ -460,7 +492,7 @@ function syncConstraintMeta(constraintsMap, constraint) {
|
|
|
460
492
|
function syncRelationshipMeta(relsMap, relName, relConfig) {
|
|
461
493
|
let relMeta = relsMap.get(relName);
|
|
462
494
|
if (!relMeta) {
|
|
463
|
-
relMeta = new
|
|
495
|
+
relMeta = new Y3.Map();
|
|
464
496
|
relsMap.set(relName, relMeta);
|
|
465
497
|
}
|
|
466
498
|
for (const [key, value] of Object.entries(relConfig)) {
|
|
@@ -475,7 +507,7 @@ function syncInferredMeta(yDoc, modelName, recordData) {
|
|
|
475
507
|
if (fieldName.startsWith("_")) continue;
|
|
476
508
|
let fieldMeta = meta.get(fieldName);
|
|
477
509
|
if (!fieldMeta) {
|
|
478
|
-
fieldMeta = new
|
|
510
|
+
fieldMeta = new Y3.Map();
|
|
479
511
|
meta.set(fieldName, fieldMeta);
|
|
480
512
|
}
|
|
481
513
|
if (!fieldMeta.has("type")) {
|
|
@@ -491,11 +523,12 @@ function setIfChanged(map, key, value) {
|
|
|
491
523
|
map.set(key, value);
|
|
492
524
|
}
|
|
493
525
|
}
|
|
494
|
-
var
|
|
526
|
+
var Y3, KNOWN_FUNCTION_DEFAULTS, _syncedCache;
|
|
495
527
|
var init_metaSync = __esm({
|
|
496
528
|
"src/models/metaSync.ts"() {
|
|
497
529
|
"use strict";
|
|
498
|
-
|
|
530
|
+
Y3 = __toESM(require("yjs"), 1);
|
|
531
|
+
init_yjsValues();
|
|
499
532
|
KNOWN_FUNCTION_DEFAULTS = /* @__PURE__ */ new WeakMap();
|
|
500
533
|
_syncedCache = /* @__PURE__ */ new WeakMap();
|
|
501
534
|
}
|
|
@@ -505,11 +538,11 @@ var init_metaSync = __esm({
|
|
|
505
538
|
function generateULID() {
|
|
506
539
|
return (0, import_ulid2.ulid)();
|
|
507
540
|
}
|
|
508
|
-
var
|
|
541
|
+
var Y4, import_ulid2;
|
|
509
542
|
var init_BaseModel = __esm({
|
|
510
543
|
"src/models/BaseModel.ts"() {
|
|
511
544
|
"use strict";
|
|
512
|
-
|
|
545
|
+
Y4 = __toESM(require("yjs"), 1);
|
|
513
546
|
import_ulid2 = require("ulid");
|
|
514
547
|
init_StringSet();
|
|
515
548
|
init_documentTypes();
|
|
@@ -517,6 +550,7 @@ var init_BaseModel = __esm({
|
|
|
517
550
|
init_CursorManager();
|
|
518
551
|
init_sql();
|
|
519
552
|
init_metaSync();
|
|
553
|
+
init_yjsValues();
|
|
520
554
|
registerFunctionDefault(generateULID, "generate_ulid");
|
|
521
555
|
}
|
|
522
556
|
});
|
|
@@ -544,9 +578,13 @@ __export(cloudflare_do_exports, {
|
|
|
544
578
|
DurableObjectEngine: () => DurableObjectEngine,
|
|
545
579
|
JsonQueryTranslator: () => JsonQueryTranslator,
|
|
546
580
|
JsonSchemaDDL: () => JsonSchemaDDL,
|
|
581
|
+
buildSafeAlias: () => buildSafeAlias,
|
|
582
|
+
checkAutoStampsReservedFields: () => checkAutoStampsReservedFields,
|
|
583
|
+
checkReservedFieldName: () => checkReservedFieldName,
|
|
547
584
|
clearMetaSyncCache: () => clearMetaSyncCache,
|
|
548
585
|
createDatabaseDO: () => createDatabaseDO,
|
|
549
586
|
createDocumentDO: () => createDocumentDO,
|
|
587
|
+
deterministicIdentifierHash: () => deterministicIdentifierHash,
|
|
550
588
|
discoverModelNames: () => discoverModelNames,
|
|
551
589
|
discoverSchema: () => discoverSchema,
|
|
552
590
|
handleRequest: () => handleRequest,
|
|
@@ -2249,6 +2287,50 @@ var JsonQueryTranslator = class {
|
|
|
2249
2287
|
init_CursorManager();
|
|
2250
2288
|
init_sql();
|
|
2251
2289
|
var import_ulid = require("ulid");
|
|
2290
|
+
function checkReservedFieldName(fieldName) {
|
|
2291
|
+
if (fieldName === "id") return null;
|
|
2292
|
+
if (fieldName === "type") {
|
|
2293
|
+
return `Field 'type' is reserved (maps to internal _type column in queries)`;
|
|
2294
|
+
}
|
|
2295
|
+
if (fieldName.startsWith("_")) {
|
|
2296
|
+
return `Field '${fieldName}' is reserved (fields starting with '_' are internal)`;
|
|
2297
|
+
}
|
|
2298
|
+
return null;
|
|
2299
|
+
}
|
|
2300
|
+
function checkAutoStampsReservedFields(autoStamps) {
|
|
2301
|
+
if (!autoStamps) return null;
|
|
2302
|
+
for (const field of autoStamps.create || []) {
|
|
2303
|
+
const err = checkReservedFieldName(field);
|
|
2304
|
+
if (err) return `autoStamps.create: ${err}`;
|
|
2305
|
+
}
|
|
2306
|
+
for (const field of autoStamps.update || []) {
|
|
2307
|
+
const err = checkReservedFieldName(field);
|
|
2308
|
+
if (err) return `autoStamps.update: ${err}`;
|
|
2309
|
+
}
|
|
2310
|
+
return null;
|
|
2311
|
+
}
|
|
2312
|
+
function applyAutoStamps(data, autoStamps, isNew) {
|
|
2313
|
+
if (!autoStamps || !data) return;
|
|
2314
|
+
const reservedError = checkAutoStampsReservedFields(autoStamps);
|
|
2315
|
+
if (reservedError) {
|
|
2316
|
+
throw new Error(reservedError);
|
|
2317
|
+
}
|
|
2318
|
+
const now = Date.now();
|
|
2319
|
+
if (isNew && autoStamps.create) {
|
|
2320
|
+
for (const field of autoStamps.create) {
|
|
2321
|
+
if (data[field] === void 0 || data[field] === null) {
|
|
2322
|
+
data[field] = now;
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
}
|
|
2326
|
+
if (autoStamps.update) {
|
|
2327
|
+
for (const field of autoStamps.update) {
|
|
2328
|
+
if (data[field] === void 0 || data[field] === null) {
|
|
2329
|
+
data[field] = now;
|
|
2330
|
+
}
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2252
2334
|
function createDatabaseDO(config = {}) {
|
|
2253
2335
|
const hooks = config.hooks;
|
|
2254
2336
|
return class DocumentDO {
|
|
@@ -2429,7 +2511,15 @@ function createDatabaseDO(config = {}) {
|
|
|
2429
2511
|
/** @internal */
|
|
2430
2512
|
async _handleSave(request, docId) {
|
|
2431
2513
|
const body = await request.json();
|
|
2432
|
-
const {
|
|
2514
|
+
const {
|
|
2515
|
+
modelName,
|
|
2516
|
+
data,
|
|
2517
|
+
stringSets,
|
|
2518
|
+
ifNotExists,
|
|
2519
|
+
condition,
|
|
2520
|
+
upsertOn,
|
|
2521
|
+
autoStamps
|
|
2522
|
+
} = body;
|
|
2433
2523
|
let id = body.id;
|
|
2434
2524
|
if (!modelName || !id && !upsertOn) {
|
|
2435
2525
|
return this._errorResponse("modelName is required, and either id or upsertOn must be provided", 400);
|
|
@@ -2443,6 +2533,12 @@ function createDatabaseDO(config = {}) {
|
|
|
2443
2533
|
return this._errorResponse(reservedError, 400);
|
|
2444
2534
|
}
|
|
2445
2535
|
}
|
|
2536
|
+
{
|
|
2537
|
+
const autoStampsReservedError = checkAutoStampsReservedFields(autoStamps);
|
|
2538
|
+
if (autoStampsReservedError) {
|
|
2539
|
+
return this._errorResponse(autoStampsReservedError, 400);
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
2446
2542
|
if (upsertOn) {
|
|
2447
2543
|
if (data[upsertOn] === void 0 || data[upsertOn] === null || data[upsertOn] === "") {
|
|
2448
2544
|
return this._errorResponse(
|
|
@@ -2492,8 +2588,9 @@ function createDatabaseDO(config = {}) {
|
|
|
2492
2588
|
409
|
|
2493
2589
|
);
|
|
2494
2590
|
}
|
|
2591
|
+
const isNew = !this._engine.recordExists(modelName, id);
|
|
2592
|
+
applyAutoStamps(data, autoStamps, isNew);
|
|
2495
2593
|
if (hooks?.beforeSave) {
|
|
2496
|
-
const isNew = !this._engine.recordExists(modelName, id);
|
|
2497
2594
|
const ctx = {
|
|
2498
2595
|
modelName,
|
|
2499
2596
|
docId,
|
|
@@ -2542,7 +2639,7 @@ function createDatabaseDO(config = {}) {
|
|
|
2542
2639
|
/** @internal — Partial update: merge provided fields into existing record */
|
|
2543
2640
|
async _handlePatch(request, docId) {
|
|
2544
2641
|
const body = await request.json();
|
|
2545
|
-
const { modelName, id, data, stringSets, condition } = body;
|
|
2642
|
+
const { modelName, id, data, stringSets, condition, autoStamps } = body;
|
|
2546
2643
|
if (!modelName || !id || !data) {
|
|
2547
2644
|
return this._errorResponse("modelName, id, and data are required", 400);
|
|
2548
2645
|
}
|
|
@@ -2550,6 +2647,12 @@ function createDatabaseDO(config = {}) {
|
|
|
2550
2647
|
if (reservedError) {
|
|
2551
2648
|
return this._errorResponse(reservedError, 400);
|
|
2552
2649
|
}
|
|
2650
|
+
{
|
|
2651
|
+
const autoStampsReservedError = checkAutoStampsReservedFields(autoStamps);
|
|
2652
|
+
if (autoStampsReservedError) {
|
|
2653
|
+
return this._errorResponse(autoStampsReservedError, 400);
|
|
2654
|
+
}
|
|
2655
|
+
}
|
|
2553
2656
|
if (!this._engine.recordExists(modelName, id)) {
|
|
2554
2657
|
return this._errorResponse(`Record ${modelName}/${id} not found`, 404);
|
|
2555
2658
|
}
|
|
@@ -2559,6 +2662,7 @@ function createDatabaseDO(config = {}) {
|
|
|
2559
2662
|
409
|
|
2560
2663
|
);
|
|
2561
2664
|
}
|
|
2665
|
+
applyAutoStamps(data, autoStamps, false);
|
|
2562
2666
|
if (hooks?.beforeSave) {
|
|
2563
2667
|
const ctx = {
|
|
2564
2668
|
modelName,
|
|
@@ -2645,6 +2749,7 @@ function createDatabaseDO(config = {}) {
|
|
|
2645
2749
|
const hookDenials = /* @__PURE__ */ new Map();
|
|
2646
2750
|
const resolvedBatchIds = /* @__PURE__ */ new Map();
|
|
2647
2751
|
const opErrors = /* @__PURE__ */ new Map();
|
|
2752
|
+
const batchCreatedIds = /* @__PURE__ */ new Set();
|
|
2648
2753
|
for (let i = 0; i < operations.length; i++) {
|
|
2649
2754
|
const op = operations[i];
|
|
2650
2755
|
if (!op.modelName || !op.id && !(op.op === "save" && op.upsertOn) || !op.op) {
|
|
@@ -2696,9 +2801,30 @@ function createDatabaseDO(config = {}) {
|
|
|
2696
2801
|
400
|
|
2697
2802
|
);
|
|
2698
2803
|
}
|
|
2804
|
+
const autoStampsReservedError = checkAutoStampsReservedFields(
|
|
2805
|
+
op.autoStamps
|
|
2806
|
+
);
|
|
2807
|
+
if (autoStampsReservedError) {
|
|
2808
|
+
return this._errorResponse(
|
|
2809
|
+
`Operation ${i}: ${autoStampsReservedError}`,
|
|
2810
|
+
400
|
|
2811
|
+
);
|
|
2812
|
+
}
|
|
2813
|
+
const effectiveId = resolvedBatchIds.get(i) || op.id || "";
|
|
2814
|
+
const isNew = op.op === "save" ? (
|
|
2815
|
+
// Codex P2 #3: also treat the id as not-new if a previous
|
|
2816
|
+
// op in this batch already resolved to a save for the same
|
|
2817
|
+
// model+id. Without this, two saves to the same fresh id
|
|
2818
|
+
// both compute isNew=true against the pre-transaction
|
|
2819
|
+
// database, so a `'create'` autoStamp on the second op
|
|
2820
|
+
// would overwrite the first op's stamp.
|
|
2821
|
+
!batchCreatedIds.has(`${op.modelName}/${effectiveId}`) && !this._engine.recordExists(op.modelName, effectiveId)
|
|
2822
|
+
) : false;
|
|
2823
|
+
if (op.op === "save" && isNew && effectiveId) {
|
|
2824
|
+
batchCreatedIds.add(`${op.modelName}/${effectiveId}`);
|
|
2825
|
+
}
|
|
2826
|
+
applyAutoStamps(op.data, op.autoStamps, isNew);
|
|
2699
2827
|
if (hooks?.beforeSave) {
|
|
2700
|
-
const effectiveId = resolvedBatchIds.get(i) || op.id || "";
|
|
2701
|
-
const isNew = op.op === "save" ? !this._engine.recordExists(op.modelName, effectiveId) : false;
|
|
2702
2828
|
const ctx = {
|
|
2703
2829
|
modelName: op.modelName,
|
|
2704
2830
|
docId,
|
|
@@ -4059,7 +4185,8 @@ async function handleRequest(request, env) {
|
|
|
4059
4185
|
}
|
|
4060
4186
|
|
|
4061
4187
|
// src/utils/yDocSchema.ts
|
|
4062
|
-
var
|
|
4188
|
+
var Y2 = __toESM(require("yjs"), 1);
|
|
4189
|
+
init_yjsValues();
|
|
4063
4190
|
function discoverSchema(yDoc) {
|
|
4064
4191
|
const models = {};
|
|
4065
4192
|
const metaNames = /* @__PURE__ */ new Set();
|
|
@@ -4093,7 +4220,7 @@ function discoverModelNames(yDoc) {
|
|
|
4093
4220
|
function materializeMap(yDoc, key) {
|
|
4094
4221
|
try {
|
|
4095
4222
|
const map = yDoc.getMap(key);
|
|
4096
|
-
return map instanceof
|
|
4223
|
+
return map instanceof Y2.Map ? map : null;
|
|
4097
4224
|
} catch {
|
|
4098
4225
|
return null;
|
|
4099
4226
|
}
|
|
@@ -4103,11 +4230,11 @@ function readModelMeta(metaMap) {
|
|
|
4103
4230
|
let constraints;
|
|
4104
4231
|
let relationships;
|
|
4105
4232
|
for (const [key, value] of metaMap.entries()) {
|
|
4106
|
-
if (key === "_constraints" && value instanceof
|
|
4233
|
+
if (key === "_constraints" && value instanceof Y2.Map) {
|
|
4107
4234
|
constraints = readConstraints(value);
|
|
4108
|
-
} else if (key === "_relationships" && value instanceof
|
|
4235
|
+
} else if (key === "_relationships" && value instanceof Y2.Map) {
|
|
4109
4236
|
relationships = readRelationships(value);
|
|
4110
|
-
} else if (value instanceof
|
|
4237
|
+
} else if (value instanceof Y2.Map) {
|
|
4111
4238
|
fields[key] = readFieldMeta(value);
|
|
4112
4239
|
}
|
|
4113
4240
|
}
|
|
@@ -4126,6 +4253,10 @@ function readFieldMeta(fieldMap) {
|
|
|
4126
4253
|
if (fieldMap.get("unique") === true) field.unique = true;
|
|
4127
4254
|
if (fieldMap.get("required") === true) field.required = true;
|
|
4128
4255
|
if (fieldMap.get("autoAssign") === true) field.autoAssign = true;
|
|
4256
|
+
const autoStamp = fieldMap.get("autoStamp");
|
|
4257
|
+
if (typeof autoStamp === "string" && (autoStamp === "create" || autoStamp === "update" || autoStamp === "both")) {
|
|
4258
|
+
field.autoStamp = autoStamp;
|
|
4259
|
+
}
|
|
4129
4260
|
const def = fieldMap.get("default");
|
|
4130
4261
|
if (def !== void 0) field.default = def;
|
|
4131
4262
|
const maxLength = fieldMap.get("maxLength");
|
|
@@ -4138,7 +4269,7 @@ function inferModelFromData(dataMap) {
|
|
|
4138
4269
|
const fields = {};
|
|
4139
4270
|
let sampled = 0;
|
|
4140
4271
|
for (const [_recordId, recordValue] of dataMap.entries()) {
|
|
4141
|
-
if (!(recordValue instanceof
|
|
4272
|
+
if (!(recordValue instanceof Y2.Map)) continue;
|
|
4142
4273
|
if (++sampled > 5) break;
|
|
4143
4274
|
for (const [fieldName, value] of recordValue.entries()) {
|
|
4144
4275
|
if (fieldName.startsWith("_")) continue;
|
|
@@ -4151,7 +4282,7 @@ function inferModelFromData(dataMap) {
|
|
|
4151
4282
|
return { fields };
|
|
4152
4283
|
}
|
|
4153
4284
|
function inferTypeFromValue(value) {
|
|
4154
|
-
if (value
|
|
4285
|
+
if (isStringSetYMap(value)) return "stringset";
|
|
4155
4286
|
switch (typeof value) {
|
|
4156
4287
|
case "string":
|
|
4157
4288
|
return "string";
|
|
@@ -4166,7 +4297,7 @@ function inferTypeFromValue(value) {
|
|
|
4166
4297
|
function readConstraints(constraintsMap) {
|
|
4167
4298
|
const out = {};
|
|
4168
4299
|
for (const [name, value] of constraintsMap.entries()) {
|
|
4169
|
-
if (!(value instanceof
|
|
4300
|
+
if (!(value instanceof Y2.Map)) continue;
|
|
4170
4301
|
let fields = [];
|
|
4171
4302
|
const rawFields = value.get("fields");
|
|
4172
4303
|
if (typeof rawFields === "string") {
|
|
@@ -4185,7 +4316,7 @@ function readConstraints(constraintsMap) {
|
|
|
4185
4316
|
function readRelationships(relsMap) {
|
|
4186
4317
|
const out = {};
|
|
4187
4318
|
for (const [name, value] of relsMap.entries()) {
|
|
4188
|
-
if (!(value instanceof
|
|
4319
|
+
if (!(value instanceof Y2.Map)) continue;
|
|
4189
4320
|
const rel = {};
|
|
4190
4321
|
for (const [k, v] of value.entries()) {
|
|
4191
4322
|
rel[k] = v;
|
|
@@ -4196,6 +4327,7 @@ function readRelationships(relsMap) {
|
|
|
4196
4327
|
}
|
|
4197
4328
|
var CAMEL_TO_SNAKE = {
|
|
4198
4329
|
autoAssign: "auto_assign",
|
|
4330
|
+
autoStamp: "auto_stamp",
|
|
4199
4331
|
maxLength: "max_length",
|
|
4200
4332
|
maxCount: "max_count",
|
|
4201
4333
|
relatedIdField: "related_id_field",
|
|
@@ -4226,6 +4358,7 @@ function schemaToToml(schema) {
|
|
|
4226
4358
|
lines.push(`[models.${modelName}.fields.${fieldName}]`);
|
|
4227
4359
|
lines.push(`type = ${tomlValue(field.type)}`);
|
|
4228
4360
|
if (field.autoAssign) lines.push("auto_assign = true");
|
|
4361
|
+
if (field.autoStamp) lines.push(`auto_stamp = ${tomlValue(field.autoStamp)}`);
|
|
4229
4362
|
if (field.indexed) lines.push("indexed = true");
|
|
4230
4363
|
if (field.unique) lines.push("unique = true");
|
|
4231
4364
|
if (field.required) lines.push("required = true");
|
|
@@ -4349,10 +4482,13 @@ var KNOWN_FIELD_KEYS = /* @__PURE__ */ new Set([
|
|
|
4349
4482
|
"unique",
|
|
4350
4483
|
"required",
|
|
4351
4484
|
"auto_assign",
|
|
4485
|
+
"auto_stamp",
|
|
4352
4486
|
"max_length",
|
|
4353
4487
|
"max_count",
|
|
4354
|
-
"default"
|
|
4488
|
+
"default",
|
|
4489
|
+
"enum"
|
|
4355
4490
|
]);
|
|
4491
|
+
var VALID_AUTO_STAMP_VALUES = /* @__PURE__ */ new Set(["create", "update", "both"]);
|
|
4356
4492
|
var KNOWN_MODEL_KEYS = /* @__PURE__ */ new Set([
|
|
4357
4493
|
"fields",
|
|
4358
4494
|
"relationships",
|
|
@@ -4394,9 +4530,37 @@ function parseFieldOptions(raw, context, strict) {
|
|
|
4394
4530
|
if (raw.unique === true) opts.unique = true;
|
|
4395
4531
|
if (raw.required === true) opts.required = true;
|
|
4396
4532
|
if (raw.auto_assign === true) opts.autoAssign = true;
|
|
4533
|
+
if (raw.auto_stamp !== void 0) {
|
|
4534
|
+
if (typeof raw.auto_stamp !== "string" || !VALID_AUTO_STAMP_VALUES.has(raw.auto_stamp)) {
|
|
4535
|
+
throw new Error(
|
|
4536
|
+
`${context}: invalid auto_stamp value "${raw.auto_stamp}". Must be one of: ${[
|
|
4537
|
+
...VALID_AUTO_STAMP_VALUES
|
|
4538
|
+
].join(", ")}`
|
|
4539
|
+
);
|
|
4540
|
+
}
|
|
4541
|
+
opts.autoStamp = raw.auto_stamp;
|
|
4542
|
+
}
|
|
4397
4543
|
if (raw.max_length !== void 0) opts.maxLength = raw.max_length;
|
|
4398
4544
|
if (raw.max_count !== void 0) opts.maxCount = raw.max_count;
|
|
4399
4545
|
if (raw.default !== void 0) opts.default = raw.default;
|
|
4546
|
+
if (raw.enum !== void 0) {
|
|
4547
|
+
if (raw.type !== "string") {
|
|
4548
|
+
throw new Error(
|
|
4549
|
+
`${context}: \`enum\` is only valid on a "string" field, not "${raw.type}".`
|
|
4550
|
+
);
|
|
4551
|
+
}
|
|
4552
|
+
if (!Array.isArray(raw.enum) || raw.enum.length === 0) {
|
|
4553
|
+
throw new Error(
|
|
4554
|
+
`${context}: \`enum\` must be a non-empty array of strings.`
|
|
4555
|
+
);
|
|
4556
|
+
}
|
|
4557
|
+
if (!raw.enum.every((v) => typeof v === "string")) {
|
|
4558
|
+
throw new Error(
|
|
4559
|
+
`${context}: \`enum\` values must all be strings.`
|
|
4560
|
+
);
|
|
4561
|
+
}
|
|
4562
|
+
opts.enum = [...raw.enum];
|
|
4563
|
+
}
|
|
4400
4564
|
return opts;
|
|
4401
4565
|
}
|
|
4402
4566
|
function requireField(raw, field, context) {
|
|
@@ -4520,3 +4684,4 @@ function loadSchemaFromTomlString(tomlString, options = {}) {
|
|
|
4520
4684
|
|
|
4521
4685
|
// src/cloudflare-do.ts
|
|
4522
4686
|
init_metaSync();
|
|
4687
|
+
init_sql();
|
package/dist/cloudflare-do.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;
|
|
@@ -1023,6 +1047,25 @@ interface SaveRequest {
|
|
|
1023
1047
|
ifNotExists?: boolean;
|
|
1024
1048
|
condition?: DocumentFilter;
|
|
1025
1049
|
upsertOn?: string;
|
|
1050
|
+
/**
|
|
1051
|
+
* Per-call auto-timestamp directives for schemaless writes.
|
|
1052
|
+
*
|
|
1053
|
+
* Each entry is a list of field names to stamp with `Date.now()`
|
|
1054
|
+
* (milliseconds) at the corresponding lifecycle event:
|
|
1055
|
+
*
|
|
1056
|
+
* - `create` — field is stamped only when the record is new
|
|
1057
|
+
* (no row exists yet for this `modelName` + `id`).
|
|
1058
|
+
* - `update` — field is stamped on every save (create AND update).
|
|
1059
|
+
*
|
|
1060
|
+
* If the caller already provided an explicit value for the field
|
|
1061
|
+
* in `data`, the explicit value wins and the stamp is skipped.
|
|
1062
|
+
* Stamping happens BEFORE the `beforeSave` user hook runs, so user
|
|
1063
|
+
* hooks may still overwrite the stamped value if they wish.
|
|
1064
|
+
*/
|
|
1065
|
+
autoStamps?: {
|
|
1066
|
+
create?: string[];
|
|
1067
|
+
update?: string[];
|
|
1068
|
+
};
|
|
1026
1069
|
}
|
|
1027
1070
|
interface DeleteRequest {
|
|
1028
1071
|
modelName: string;
|
|
@@ -1131,6 +1174,17 @@ interface PatchRequest {
|
|
|
1131
1174
|
data: Record<string, any>;
|
|
1132
1175
|
stringSets?: Record<string, string[]>;
|
|
1133
1176
|
condition?: DocumentFilter;
|
|
1177
|
+
/**
|
|
1178
|
+
* Per-call auto-timestamp directives. A patch is always treated as
|
|
1179
|
+
* an update (`isNew === false`), so only the `update` list is
|
|
1180
|
+
* applied; the `create` list is ignored on patch.
|
|
1181
|
+
*
|
|
1182
|
+
* Explicit values in `data` win.
|
|
1183
|
+
*/
|
|
1184
|
+
autoStamps?: {
|
|
1185
|
+
create?: string[];
|
|
1186
|
+
update?: string[];
|
|
1187
|
+
};
|
|
1134
1188
|
}
|
|
1135
1189
|
interface PatchResponse {
|
|
1136
1190
|
success: boolean;
|
|
@@ -1146,6 +1200,19 @@ interface BatchOperation {
|
|
|
1146
1200
|
ifNotExists?: boolean;
|
|
1147
1201
|
condition?: DocumentFilter;
|
|
1148
1202
|
upsertOn?: string;
|
|
1203
|
+
/**
|
|
1204
|
+
* Per-operation auto-timestamp directives. Same semantics as
|
|
1205
|
+
* `SaveRequest.autoStamps` / `PatchRequest.autoStamps`. Applied
|
|
1206
|
+
* BEFORE the user `beforeSave` hook runs, with explicit values
|
|
1207
|
+
* in `data` always winning.
|
|
1208
|
+
*
|
|
1209
|
+
* For `op: "patch"`, only the `update` list applies (patches are
|
|
1210
|
+
* always treated as updates).
|
|
1211
|
+
*/
|
|
1212
|
+
autoStamps?: {
|
|
1213
|
+
create?: string[];
|
|
1214
|
+
update?: string[];
|
|
1215
|
+
};
|
|
1149
1216
|
}
|
|
1150
1217
|
interface BatchRequest {
|
|
1151
1218
|
operations: BatchOperation[];
|
|
@@ -1186,6 +1253,7 @@ interface SchemaFieldInfo {
|
|
|
1186
1253
|
required?: boolean;
|
|
1187
1254
|
default?: string | number | boolean;
|
|
1188
1255
|
autoAssign?: boolean;
|
|
1256
|
+
autoStamp?: "create" | "update" | "both";
|
|
1189
1257
|
maxLength?: number;
|
|
1190
1258
|
maxCount?: number;
|
|
1191
1259
|
}
|
|
@@ -1207,6 +1275,35 @@ interface SchemaModelInfo {
|
|
|
1207
1275
|
interface SchemaResponse {
|
|
1208
1276
|
models: Record<string, SchemaModelInfo>;
|
|
1209
1277
|
}
|
|
1278
|
+
/**
|
|
1279
|
+
* Return an error message if `fieldName` is a reserved field name
|
|
1280
|
+
* (matches the policy enforced by `_checkReservedFields` on incoming
|
|
1281
|
+
* record data). Returns `null` if the name is acceptable.
|
|
1282
|
+
*
|
|
1283
|
+
* "id" is allowed (stripped before storage in the JSON column).
|
|
1284
|
+
* "type" maps to the internal `_type` column. Any name starting with
|
|
1285
|
+
* "_" is internal.
|
|
1286
|
+
*
|
|
1287
|
+
* @internal
|
|
1288
|
+
*/
|
|
1289
|
+
declare function checkReservedFieldName(fieldName: string): string | null;
|
|
1290
|
+
/**
|
|
1291
|
+
* Validate that none of the field names listed in `autoStamps.create` /
|
|
1292
|
+
* `autoStamps.update` are reserved. Returns an error message on the
|
|
1293
|
+
* first violation, or `null` if everything is acceptable.
|
|
1294
|
+
*
|
|
1295
|
+
* Codex P2 #2: `_checkReservedFields` only inspects the keys already
|
|
1296
|
+
* present in `data`. Without this check, a request such as
|
|
1297
|
+
* `{ autoStamps: { update: ["type"] } }` would pass the data-level
|
|
1298
|
+
* guard and then have `applyAutoStamps` add a reserved key after the
|
|
1299
|
+
* fact, bypassing the protection.
|
|
1300
|
+
*
|
|
1301
|
+
* @internal
|
|
1302
|
+
*/
|
|
1303
|
+
declare function checkAutoStampsReservedFields(autoStamps: {
|
|
1304
|
+
create?: string[];
|
|
1305
|
+
update?: string[];
|
|
1306
|
+
} | undefined): string | null;
|
|
1210
1307
|
/**
|
|
1211
1308
|
* Create a Durable Object class for schemaless database storage.
|
|
1212
1309
|
*
|
|
@@ -1432,6 +1529,7 @@ interface DiscoveredField {
|
|
|
1432
1529
|
required?: boolean;
|
|
1433
1530
|
default?: string | number | boolean;
|
|
1434
1531
|
autoAssign?: boolean;
|
|
1532
|
+
autoStamp?: "create" | "update" | "both";
|
|
1435
1533
|
maxLength?: number;
|
|
1436
1534
|
maxCount?: number;
|
|
1437
1535
|
}
|
|
@@ -1676,6 +1774,17 @@ declare class BaseModel implements StringSetChangeTracker {
|
|
|
1676
1774
|
private ensureLocalChanges;
|
|
1677
1775
|
private hasLocalChange;
|
|
1678
1776
|
private getFromYjs;
|
|
1777
|
+
/**
|
|
1778
|
+
* Defensive normalization for the read/sync boundary (issue #625).
|
|
1779
|
+
*
|
|
1780
|
+
* Stringset fields are owned by getStringSetFromYjs (which expects the
|
|
1781
|
+
* raw Y.Map / legacy plain-object shape — #561 / #601), so they pass
|
|
1782
|
+
* through untouched. For any other field, a composite Yjs primitive
|
|
1783
|
+
* (Y.Map / Y.Array / Y.Text) must never reach user code or the DB layer
|
|
1784
|
+
* raw: normalize it to its plain-JS projection (toJSON / toString) and
|
|
1785
|
+
* warn, rather than throwing. Scalars pass through unchanged.
|
|
1786
|
+
*/
|
|
1787
|
+
private normalizeNonStringSetValue;
|
|
1679
1788
|
private getValue;
|
|
1680
1789
|
private setValue;
|
|
1681
1790
|
get isDirty(): boolean;
|
|
@@ -1910,6 +2019,11 @@ declare function loadSchemaFromTomlString(tomlString: string, options?: LoadSche
|
|
|
1910
2019
|
|
|
1911
2020
|
/**
|
|
1912
2021
|
* Infer a _meta_ type string from a JS runtime value.
|
|
2022
|
+
*
|
|
2023
|
+
* Only an all-`true` Y.Map (the stringset wire shape) is tagged as
|
|
2024
|
+
* `stringset` (issue #625) — a Y.Map carrying a composite payload, or a
|
|
2025
|
+
* Y.Array / Y.Text, is not a stringset and stays untyped (`null`) rather
|
|
2026
|
+
* than being mis-tagged.
|
|
1913
2027
|
*/
|
|
1914
2028
|
declare function inferFieldType(value: unknown): "string" | "number" | "boolean" | "stringset" | null;
|
|
1915
2029
|
/**
|
|
@@ -1941,4 +2055,16 @@ declare function syncModelMeta(yDoc: Y.Doc, modelName: string, schema: ModelSche
|
|
|
1941
2055
|
*/
|
|
1942
2056
|
declare function syncInferredMeta(yDoc: Y.Doc, modelName: string, recordData: Record<string, any>): void;
|
|
1943
2057
|
|
|
1944
|
-
|
|
2058
|
+
/**
|
|
2059
|
+
* Produce a deterministic, SQL-safe alias suffix derived from arbitrary input.
|
|
2060
|
+
* The resulting string always matches our identifier policy when prefixed with
|
|
2061
|
+
* a valid identifier segment.
|
|
2062
|
+
*/
|
|
2063
|
+
declare function deterministicIdentifierHash(value: string): string;
|
|
2064
|
+
/**
|
|
2065
|
+
* Build a safe alias using a prefix that already conforms to the identifier
|
|
2066
|
+
* policy along with a deterministic hash of the raw descriptor.
|
|
2067
|
+
*/
|
|
2068
|
+
declare function buildSafeAlias(prefix: string, rawDescriptor: string): string;
|
|
2069
|
+
|
|
2070
|
+
export { type AfterQueryContext, type AfterQueryResult, type AggregateRequest, type AggregateResponse, type AggregationOperation$1 as AggregationOperation, type AggregationOptions$1 as AggregationOptions, type AggregationResult$1 as AggregationResult, type BatchOperation, type BatchOperationResult, type BatchRequest, type BatchResponse, type BeforeDeleteContext, type BeforeQueryContext, type BeforeQueryResult, type BeforeSaveContext, type CountRequest, type CountResponse, type DatabaseDOConfig, type DatabaseDOHooks, type DeleteRequest, type DeleteResponse, type DiscoveredConstraint, type DiscoveredField, type DiscoveredModel, type DiscoveredRelationship, type DiscoveredSchema, type DocumentDOConfig, type DropIndexRequest, type DropIndexResponse, type DropUniqueConstraintRequest, type DropUniqueConstraintResponse, DurableObjectEngine, type DurableObjectEngineOptions, type DurableObjectId, type DurableObjectNamespace, type DurableObjectState, type DurableObjectStorage, type DurableObjectStub, type Env, type ErrorResponse, type GroupByField$1 as GroupByField, type HookContext, type HookResult, type IncrementRequest, type IncrementResponse, type IndexEntry, type IndexListResponse, JsonQueryTranslator, type JsonQueryTranslatorOptions, JsonSchemaDDL, type JsonSchemaOptions, type PatchRequest, type PatchResponse, type QueryRequest, type QueryResponse, type RegisterIndexRequest, type RegisterIndexResponse, type RegisterUniqueConstraintRequest, type RegisterUniqueConstraintResponse, type SaveRequest, type SaveResponse, type SchemaConstraintInfo, type SchemaFieldInfo, type SchemaModelInfo, type SchemaResponse, type SqlStorage, type SqlStorageCursor, type StringSetMembership$1 as StringSetMembership, type StringSetUpdateRequest, type StringSetUpdateResponse, type SyncIndexesRequest, type SyncIndexesResponse, type UniqueConstraintEntry, type UniqueConstraintListResponse, buildSafeAlias, checkAutoStampsReservedFields, checkReservedFieldName, clearMetaSyncCache, createDatabaseDO, createDocumentDO, deterministicIdentifierHash, discoverModelNames, discoverSchema, handleRequest, inferFieldType, loadSchemaFromTomlString, registerFunctionDefault, schemaToToml, syncInferredMeta, syncModelMeta };
|