js-bao 0.3.0-alpha.4 → 0.3.0-alpha.5
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/cloudflare-do.cjs +173 -126
- package/dist/cloudflare-do.d.cts +12 -6
- package/dist/cloudflare-do.d.ts +12 -6
- package/dist/cloudflare-do.js +173 -126
- package/dist/codegen.cjs +1 -1
- package/package.json +1 -1
package/dist/cloudflare-do.cjs
CHANGED
|
@@ -92,20 +92,20 @@ var JsonSchemaDDL = class {
|
|
|
92
92
|
if (options.includeDocIdColumn) {
|
|
93
93
|
return `
|
|
94
94
|
CREATE TABLE IF NOT EXISTS records (
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
95
|
+
_id TEXT NOT NULL,
|
|
96
|
+
_type TEXT NOT NULL,
|
|
97
|
+
_data TEXT NOT NULL,
|
|
98
98
|
_meta_doc_id TEXT,
|
|
99
99
|
_meta_permission_hint TEXT,
|
|
100
|
-
PRIMARY KEY (
|
|
100
|
+
PRIMARY KEY (_type, _id)
|
|
101
101
|
)`.trim();
|
|
102
102
|
} else {
|
|
103
103
|
return `
|
|
104
104
|
CREATE TABLE IF NOT EXISTS records (
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
PRIMARY KEY (
|
|
105
|
+
_id TEXT NOT NULL,
|
|
106
|
+
_type TEXT NOT NULL,
|
|
107
|
+
_data TEXT NOT NULL,
|
|
108
|
+
PRIMARY KEY (_type, _id)
|
|
109
109
|
)`.trim();
|
|
110
110
|
}
|
|
111
111
|
}
|
|
@@ -116,21 +116,21 @@ CREATE TABLE IF NOT EXISTS records (
|
|
|
116
116
|
if (options.includeDocIdColumn) {
|
|
117
117
|
return `
|
|
118
118
|
CREATE TABLE IF NOT EXISTS stringset_index (
|
|
119
|
-
|
|
120
|
-
|
|
119
|
+
_record_id TEXT NOT NULL,
|
|
120
|
+
_type TEXT NOT NULL,
|
|
121
121
|
field TEXT NOT NULL,
|
|
122
122
|
value TEXT NOT NULL,
|
|
123
123
|
_meta_doc_id TEXT,
|
|
124
|
-
UNIQUE(
|
|
124
|
+
UNIQUE(_type, field, _record_id, value)
|
|
125
125
|
)`.trim();
|
|
126
126
|
} else {
|
|
127
127
|
return `
|
|
128
128
|
CREATE TABLE IF NOT EXISTS stringset_index (
|
|
129
|
-
|
|
130
|
-
|
|
129
|
+
_record_id TEXT NOT NULL,
|
|
130
|
+
_type TEXT NOT NULL,
|
|
131
131
|
field TEXT NOT NULL,
|
|
132
132
|
value TEXT NOT NULL,
|
|
133
|
-
UNIQUE(
|
|
133
|
+
UNIQUE(_type, field, _record_id, value)
|
|
134
134
|
)`.trim();
|
|
135
135
|
}
|
|
136
136
|
}
|
|
@@ -139,11 +139,11 @@ CREATE TABLE IF NOT EXISTS stringset_index (
|
|
|
139
139
|
*/
|
|
140
140
|
static createBaseIndexes(options) {
|
|
141
141
|
const indexes = [
|
|
142
|
-
// Index on
|
|
143
|
-
"CREATE INDEX IF NOT EXISTS idx_records_type ON records(
|
|
142
|
+
// Index on _type for filtering by model
|
|
143
|
+
"CREATE INDEX IF NOT EXISTS idx_records_type ON records(_type)",
|
|
144
144
|
// StringSet indexes for efficient lookups
|
|
145
|
-
"CREATE INDEX IF NOT EXISTS idx_ss_type_field_value ON stringset_index(
|
|
146
|
-
"CREATE INDEX IF NOT EXISTS idx_ss_type_field_record ON stringset_index(
|
|
145
|
+
"CREATE INDEX IF NOT EXISTS idx_ss_type_field_value ON stringset_index(_type, field, value)",
|
|
146
|
+
"CREATE INDEX IF NOT EXISTS idx_ss_type_field_record ON stringset_index(_type, field, _record_id)"
|
|
147
147
|
];
|
|
148
148
|
if (options.includeDocIdColumn) {
|
|
149
149
|
indexes.push(
|
|
@@ -161,7 +161,7 @@ CREATE TABLE IF NOT EXISTS stringset_index (
|
|
|
161
161
|
assertValidIdentifier(modelName, "createFieldIndex modelName");
|
|
162
162
|
assertValidIdentifier(fieldName, "createFieldIndex fieldName");
|
|
163
163
|
const indexName = `idx_records_${modelName.toLowerCase()}_${fieldName.toLowerCase()}`;
|
|
164
|
-
return `CREATE INDEX IF NOT EXISTS ${indexName} ON records(json_extract(
|
|
164
|
+
return `CREATE INDEX IF NOT EXISTS ${indexName} ON records(json_extract(_data, '$.${fieldName}')) WHERE _type = '${modelName}'`;
|
|
165
165
|
}
|
|
166
166
|
/**
|
|
167
167
|
* Generate DROP INDEX statement for a model field
|
|
@@ -234,11 +234,11 @@ CREATE TABLE IF NOT EXISTS _model_fields (
|
|
|
234
234
|
static buildInsertSQL(options) {
|
|
235
235
|
if (options.includeDocIdColumn) {
|
|
236
236
|
return `
|
|
237
|
-
INSERT OR REPLACE INTO records (
|
|
237
|
+
INSERT OR REPLACE INTO records (_id, _type, _data, _meta_doc_id, _meta_permission_hint)
|
|
238
238
|
VALUES (?, ?, ?, ?, ?)`.trim();
|
|
239
239
|
} else {
|
|
240
240
|
return `
|
|
241
|
-
INSERT OR REPLACE INTO records (
|
|
241
|
+
INSERT OR REPLACE INTO records (_id, _type, _data)
|
|
242
242
|
VALUES (?, ?, ?)`.trim();
|
|
243
243
|
}
|
|
244
244
|
}
|
|
@@ -247,16 +247,16 @@ VALUES (?, ?, ?)`.trim();
|
|
|
247
247
|
*/
|
|
248
248
|
static buildDeleteSQL(options) {
|
|
249
249
|
if (options.includeDocIdColumn) {
|
|
250
|
-
return "DELETE FROM records WHERE
|
|
250
|
+
return "DELETE FROM records WHERE _type = ? AND _id = ?";
|
|
251
251
|
} else {
|
|
252
|
-
return "DELETE FROM records WHERE
|
|
252
|
+
return "DELETE FROM records WHERE _type = ? AND _id = ?";
|
|
253
253
|
}
|
|
254
254
|
}
|
|
255
255
|
/**
|
|
256
256
|
* Generate DELETE statement for stringset values
|
|
257
257
|
*/
|
|
258
258
|
static buildDeleteStringSetSQL() {
|
|
259
|
-
return "DELETE FROM stringset_index WHERE
|
|
259
|
+
return "DELETE FROM stringset_index WHERE _type = ? AND _record_id = ?";
|
|
260
260
|
}
|
|
261
261
|
/**
|
|
262
262
|
* Generate INSERT statement for stringset values
|
|
@@ -264,11 +264,11 @@ VALUES (?, ?, ?)`.trim();
|
|
|
264
264
|
static buildInsertStringSetSQL(options) {
|
|
265
265
|
if (options.includeDocIdColumn) {
|
|
266
266
|
return `
|
|
267
|
-
INSERT OR IGNORE INTO stringset_index (
|
|
267
|
+
INSERT OR IGNORE INTO stringset_index (_record_id, _type, field, value, _meta_doc_id)
|
|
268
268
|
VALUES (?, ?, ?, ?, ?)`.trim();
|
|
269
269
|
} else {
|
|
270
270
|
return `
|
|
271
|
-
INSERT OR IGNORE INTO stringset_index (
|
|
271
|
+
INSERT OR IGNORE INTO stringset_index (_record_id, _type, field, value)
|
|
272
272
|
VALUES (?, ?, ?, ?)`.trim();
|
|
273
273
|
}
|
|
274
274
|
}
|
|
@@ -320,8 +320,7 @@ var JsonSchemaEngine = class extends DatabaseEngine {
|
|
|
320
320
|
*/
|
|
321
321
|
async insert(modelName, data) {
|
|
322
322
|
await this.initializeSchema();
|
|
323
|
-
const { id, _meta_doc_id, _meta_permission_hint, ...
|
|
324
|
-
const { type: _type, ...fieldsForJson } = rest;
|
|
323
|
+
const { id, _meta_doc_id, _meta_permission_hint, ...fieldsForJson } = data;
|
|
325
324
|
const dataJson = JSON.stringify(fieldsForJson);
|
|
326
325
|
const sql = JsonSchemaDDL.buildInsertSQL(this.schemaOptions);
|
|
327
326
|
if (this.schemaOptions.includeDocIdColumn) {
|
|
@@ -356,11 +355,11 @@ var JsonSchemaEngine = class extends DatabaseEngine {
|
|
|
356
355
|
);
|
|
357
356
|
}
|
|
358
357
|
await this.execSql(
|
|
359
|
-
"DELETE FROM records WHERE
|
|
358
|
+
"DELETE FROM records WHERE _type = ? AND _meta_doc_id = ?",
|
|
360
359
|
[modelName, docId]
|
|
361
360
|
);
|
|
362
361
|
await this.execSql(
|
|
363
|
-
"DELETE FROM stringset_index WHERE
|
|
362
|
+
"DELETE FROM stringset_index WHERE _type = ? AND _meta_doc_id = ?",
|
|
364
363
|
[modelName, docId]
|
|
365
364
|
);
|
|
366
365
|
}
|
|
@@ -390,7 +389,7 @@ var JsonSchemaEngine = class extends DatabaseEngine {
|
|
|
390
389
|
async removeStringSetValues(modelName, fieldName, recordId, values) {
|
|
391
390
|
if (values.length === 0) return;
|
|
392
391
|
const placeholders = values.map(() => "?").join(", ");
|
|
393
|
-
const sql = `DELETE FROM stringset_index WHERE
|
|
392
|
+
const sql = `DELETE FROM stringset_index WHERE _type = ? AND field = ? AND _record_id = ? AND value IN (${placeholders})`;
|
|
394
393
|
await this.execSql(sql, [modelName, fieldName, recordId, ...values]);
|
|
395
394
|
}
|
|
396
395
|
/**
|
|
@@ -441,22 +440,22 @@ var JsonSchemaEngine = class extends DatabaseEngine {
|
|
|
441
440
|
}
|
|
442
441
|
/**
|
|
443
442
|
* Parse a record row from the database.
|
|
444
|
-
* Extracts fields from
|
|
443
|
+
* Extracts fields from _data and merges with direct columns.
|
|
445
444
|
*/
|
|
446
445
|
parseRecordRow(row) {
|
|
447
446
|
if (!row) return row;
|
|
448
|
-
const {
|
|
447
|
+
const { _id, _type, _data, _meta_doc_id, _meta_permission_hint, ...rest } = row;
|
|
449
448
|
let parsedData = {};
|
|
450
|
-
if (
|
|
449
|
+
if (_data) {
|
|
451
450
|
try {
|
|
452
|
-
parsedData = JSON.parse(
|
|
451
|
+
parsedData = JSON.parse(_data);
|
|
453
452
|
} catch (e) {
|
|
454
|
-
console.warn("Failed to parse
|
|
453
|
+
console.warn("Failed to parse _data:", e);
|
|
455
454
|
}
|
|
456
455
|
}
|
|
457
456
|
const result = {
|
|
458
|
-
id,
|
|
459
|
-
type,
|
|
457
|
+
id: _id,
|
|
458
|
+
type: _type,
|
|
460
459
|
...parsedData,
|
|
461
460
|
...rest
|
|
462
461
|
// Include any projected fields
|
|
@@ -508,6 +507,55 @@ var DurableObjectEngine = class extends JsonSchemaEngine {
|
|
|
508
507
|
PRIMARY KEY (model_name, field_name)
|
|
509
508
|
)
|
|
510
509
|
`);
|
|
510
|
+
const recordsCols = this.execSqlSync("PRAGMA table_info(records)");
|
|
511
|
+
const hasOldSchema = recordsCols.some((c) => c.name === "id");
|
|
512
|
+
if (hasOldSchema) {
|
|
513
|
+
this.execSqlSync("ALTER TABLE records RENAME COLUMN id TO _id");
|
|
514
|
+
this.execSqlSync("ALTER TABLE records RENAME COLUMN type TO _type");
|
|
515
|
+
this.execSqlSync("ALTER TABLE records RENAME COLUMN data_json TO _data");
|
|
516
|
+
this.execSqlSync("ALTER TABLE stringset_index RENAME COLUMN record_id TO _record_id");
|
|
517
|
+
this.execSqlSync("ALTER TABLE stringset_index RENAME COLUMN type TO _type");
|
|
518
|
+
this.execSqlSync("DROP INDEX IF EXISTS idx_records_type");
|
|
519
|
+
this.execSqlSync("CREATE INDEX IF NOT EXISTS idx_records_type ON records(_type)");
|
|
520
|
+
this.execSqlSync("DROP INDEX IF EXISTS idx_ss_type_field_value");
|
|
521
|
+
this.execSqlSync("CREATE INDEX IF NOT EXISTS idx_ss_type_field_value ON stringset_index(_type, field, value)");
|
|
522
|
+
this.execSqlSync("DROP INDEX IF EXISTS idx_ss_type_field_record");
|
|
523
|
+
this.execSqlSync("CREATE INDEX IF NOT EXISTS idx_ss_type_field_record ON stringset_index(_type, field, _record_id)");
|
|
524
|
+
try {
|
|
525
|
+
const indexes = this.execSqlSync(
|
|
526
|
+
"SELECT model_name, field_name FROM _indexes"
|
|
527
|
+
);
|
|
528
|
+
for (const row of indexes) {
|
|
529
|
+
const mn = row.model_name;
|
|
530
|
+
const fn = row.field_name;
|
|
531
|
+
const idxName = `idx_records_${mn.toLowerCase()}_${fn.toLowerCase()}`;
|
|
532
|
+
this.execSqlSync(`DROP INDEX IF EXISTS ${idxName}`);
|
|
533
|
+
this.execSqlSync(
|
|
534
|
+
`CREATE INDEX IF NOT EXISTS ${idxName} ON records(json_extract(_data, '$.${fn}')) WHERE _type = '${mn}'`
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
} catch (e) {
|
|
538
|
+
console.debug("Could not recreate field indexes during migration:", e);
|
|
539
|
+
}
|
|
540
|
+
try {
|
|
541
|
+
const constraints = this.execSqlSync(
|
|
542
|
+
"SELECT model_name, constraint_name, fields_json FROM _unique_constraints"
|
|
543
|
+
);
|
|
544
|
+
for (const row of constraints) {
|
|
545
|
+
const mn = row.model_name;
|
|
546
|
+
const cn = row.constraint_name;
|
|
547
|
+
const fields = JSON.parse(row.fields_json);
|
|
548
|
+
const idxName = `idx_uc_${mn.toLowerCase()}_${cn.toLowerCase()}`;
|
|
549
|
+
this.execSqlSync(`DROP INDEX IF EXISTS ${idxName}`);
|
|
550
|
+
const fieldExprs = fields.map((f) => `json_extract(_data, '$.${f}')`).join(", ");
|
|
551
|
+
this.execSqlSync(
|
|
552
|
+
`CREATE INDEX IF NOT EXISTS ${idxName} ON records(${fieldExprs}) WHERE _type = '${mn}'`
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
} catch (e) {
|
|
556
|
+
console.debug("Could not recreate unique constraint indexes during migration:", e);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
511
559
|
}
|
|
512
560
|
/**
|
|
513
561
|
* Load indexes from the _indexes table and re-ensure they exist.
|
|
@@ -595,10 +643,10 @@ var DurableObjectEngine = class extends JsonSchemaEngine {
|
|
|
595
643
|
VALUES (?, ?, ?)`,
|
|
596
644
|
[modelName, constraintName, JSON.stringify(fields)]
|
|
597
645
|
);
|
|
598
|
-
const fieldExprs = fields.map((f) => `json_extract(
|
|
646
|
+
const fieldExprs = fields.map((f) => `json_extract(_data, '$.${f}')`).join(", ");
|
|
599
647
|
const indexName = `idx_uc_${modelName.toLowerCase()}_${constraintName.toLowerCase()}`;
|
|
600
648
|
this.execSqlSync(
|
|
601
|
-
`CREATE INDEX IF NOT EXISTS ${indexName} ON records(${fieldExprs}) WHERE
|
|
649
|
+
`CREATE INDEX IF NOT EXISTS ${indexName} ON records(${fieldExprs}) WHERE _type = '${modelName}'`
|
|
602
650
|
);
|
|
603
651
|
}
|
|
604
652
|
/**
|
|
@@ -638,11 +686,11 @@ var DurableObjectEngine = class extends JsonSchemaEngine {
|
|
|
638
686
|
const value = data[idx.field_name];
|
|
639
687
|
if (value === null || value === void 0) continue;
|
|
640
688
|
const conflicts = this.execSqlSync(
|
|
641
|
-
`SELECT
|
|
689
|
+
`SELECT _id FROM records WHERE _type = ? AND json_extract(_data, '$.${idx.field_name}') = ? AND _id != ? LIMIT 1`,
|
|
642
690
|
[modelName, value, id]
|
|
643
691
|
);
|
|
644
692
|
if (conflicts.length > 0) {
|
|
645
|
-
return `Unique constraint violated on field '${idx.field_name}' for model '${modelName}'. Value '${String(value).substring(0, 50)}' already exists on record '${conflicts[0].
|
|
693
|
+
return `Unique constraint violated on field '${idx.field_name}' for model '${modelName}'. Value '${String(value).substring(0, 50)}' already exists on record '${conflicts[0]._id}'.`;
|
|
646
694
|
}
|
|
647
695
|
}
|
|
648
696
|
const composites = this.listUniqueConstraints(modelName);
|
|
@@ -652,13 +700,13 @@ var DurableObjectEngine = class extends JsonSchemaEngine {
|
|
|
652
700
|
}
|
|
653
701
|
const values = constraint.fields.map((f) => data[f]);
|
|
654
702
|
if (values.some((v) => v === null || v === void 0)) continue;
|
|
655
|
-
const conditions = constraint.fields.map((f) => `json_extract(
|
|
703
|
+
const conditions = constraint.fields.map((f) => `json_extract(_data, '$.${f}') = ?`).join(" AND ");
|
|
656
704
|
const conflicts = this.execSqlSync(
|
|
657
|
-
`SELECT
|
|
705
|
+
`SELECT _id FROM records WHERE _type = ? AND ${conditions} AND _id != ? LIMIT 1`,
|
|
658
706
|
[modelName, ...values, id]
|
|
659
707
|
);
|
|
660
708
|
if (conflicts.length > 0) {
|
|
661
|
-
return `Unique constraint '${constraint.constraint_name}' violated for model '${modelName}' on fields [${constraint.fields.join(", ")}]. Values already exist on record '${conflicts[0].
|
|
709
|
+
return `Unique constraint '${constraint.constraint_name}' violated for model '${modelName}' on fields [${constraint.fields.join(", ")}]. Values already exist on record '${conflicts[0]._id}'.`;
|
|
662
710
|
}
|
|
663
711
|
}
|
|
664
712
|
return null;
|
|
@@ -668,7 +716,7 @@ var DurableObjectEngine = class extends JsonSchemaEngine {
|
|
|
668
716
|
*/
|
|
669
717
|
recordExists(modelName, id) {
|
|
670
718
|
const rows = this.execSqlSync(
|
|
671
|
-
"SELECT 1 FROM records WHERE
|
|
719
|
+
"SELECT 1 FROM records WHERE _type = ? AND _id = ? LIMIT 1",
|
|
672
720
|
[modelName, id]
|
|
673
721
|
);
|
|
674
722
|
return rows.length > 0;
|
|
@@ -726,25 +774,24 @@ var DurableObjectEngine = class extends JsonSchemaEngine {
|
|
|
726
774
|
*/
|
|
727
775
|
async insertWithStringSets(modelName, data, stringSets) {
|
|
728
776
|
this.storage.transactionSync(() => {
|
|
729
|
-
const { id, ...
|
|
730
|
-
const { type: _type, ...fieldsForJson } = rest;
|
|
777
|
+
const { id, ...fieldsForJson } = data;
|
|
731
778
|
const jsonFields = { ...fieldsForJson, ...stringSets };
|
|
732
779
|
const dataJson = JSON.stringify(jsonFields);
|
|
733
780
|
this.sql.exec(
|
|
734
|
-
"INSERT OR REPLACE INTO records (
|
|
781
|
+
"INSERT OR REPLACE INTO records (_id, _type, _data) VALUES (?, ?, ?)",
|
|
735
782
|
id,
|
|
736
783
|
modelName,
|
|
737
784
|
dataJson
|
|
738
785
|
);
|
|
739
786
|
this.sql.exec(
|
|
740
|
-
"DELETE FROM stringset_index WHERE
|
|
787
|
+
"DELETE FROM stringset_index WHERE _type = ? AND _record_id = ?",
|
|
741
788
|
modelName,
|
|
742
789
|
id
|
|
743
790
|
);
|
|
744
791
|
for (const [fieldName, values] of Object.entries(stringSets)) {
|
|
745
792
|
for (const value of values) {
|
|
746
793
|
this.sql.exec(
|
|
747
|
-
"INSERT OR IGNORE INTO stringset_index (
|
|
794
|
+
"INSERT OR IGNORE INTO stringset_index (_record_id, _type, field, value) VALUES (?, ?, ?, ?)",
|
|
748
795
|
id,
|
|
749
796
|
modelName,
|
|
750
797
|
fieldName,
|
|
@@ -772,7 +819,7 @@ var DurableObjectEngine = class extends JsonSchemaEngine {
|
|
|
772
819
|
for (const [field, values] of Object.entries(sets)) {
|
|
773
820
|
for (const value of values) {
|
|
774
821
|
this.sql.exec(
|
|
775
|
-
"INSERT OR IGNORE INTO stringset_index (
|
|
822
|
+
"INSERT OR IGNORE INTO stringset_index (_record_id, _type, field, value) VALUES (?, ?, ?, ?)",
|
|
776
823
|
id,
|
|
777
824
|
modelName,
|
|
778
825
|
field,
|
|
@@ -800,7 +847,7 @@ var DurableObjectEngine = class extends JsonSchemaEngine {
|
|
|
800
847
|
for (const [field, values] of Object.entries(sets)) {
|
|
801
848
|
for (const value of values) {
|
|
802
849
|
this.sql.exec(
|
|
803
|
-
"DELETE FROM stringset_index WHERE
|
|
850
|
+
"DELETE FROM stringset_index WHERE _record_id = ? AND _type = ? AND field = ? AND value = ?",
|
|
804
851
|
id,
|
|
805
852
|
modelName,
|
|
806
853
|
field,
|
|
@@ -816,20 +863,20 @@ var DurableObjectEngine = class extends JsonSchemaEngine {
|
|
|
816
863
|
*/
|
|
817
864
|
_syncStringSetsToJson(modelName, id, fields) {
|
|
818
865
|
const rows = this.execSqlSync(
|
|
819
|
-
"SELECT
|
|
866
|
+
"SELECT _data FROM records WHERE _id = ? AND _type = ?",
|
|
820
867
|
[id, modelName]
|
|
821
868
|
);
|
|
822
869
|
if (rows.length === 0) return;
|
|
823
|
-
const data = JSON.parse(rows[0].
|
|
870
|
+
const data = JSON.parse(rows[0]._data);
|
|
824
871
|
for (const field of fields) {
|
|
825
872
|
const ssRows = this.execSqlSync(
|
|
826
|
-
"SELECT value FROM stringset_index WHERE
|
|
873
|
+
"SELECT value FROM stringset_index WHERE _record_id = ? AND _type = ? AND field = ? ORDER BY value",
|
|
827
874
|
[id, modelName, field]
|
|
828
875
|
);
|
|
829
876
|
data[field] = ssRows.map((r) => r.value);
|
|
830
877
|
}
|
|
831
878
|
this.sql.exec(
|
|
832
|
-
"UPDATE records SET
|
|
879
|
+
"UPDATE records SET _data = ? WHERE _id = ? AND _type = ?",
|
|
833
880
|
JSON.stringify(data),
|
|
834
881
|
id,
|
|
835
882
|
modelName
|
|
@@ -855,14 +902,14 @@ var DurableObjectEngine = class extends JsonSchemaEngine {
|
|
|
855
902
|
*/
|
|
856
903
|
patchRecordRaw(modelName, id, fields, stringSets) {
|
|
857
904
|
const rows = this.execSqlSync(
|
|
858
|
-
"SELECT
|
|
905
|
+
"SELECT _data FROM records WHERE _id = ? AND _type = ?",
|
|
859
906
|
[id, modelName]
|
|
860
907
|
);
|
|
861
908
|
if (rows.length === 0) {
|
|
862
909
|
return false;
|
|
863
910
|
}
|
|
864
|
-
const existing = JSON.parse(rows[0].
|
|
865
|
-
const { id:
|
|
911
|
+
const existing = JSON.parse(rows[0]._data);
|
|
912
|
+
const { id: _stripId, ...patchFields } = fields;
|
|
866
913
|
const merged = { ...existing, ...patchFields };
|
|
867
914
|
if (stringSets) {
|
|
868
915
|
for (const [field, values] of Object.entries(stringSets)) {
|
|
@@ -870,7 +917,7 @@ var DurableObjectEngine = class extends JsonSchemaEngine {
|
|
|
870
917
|
}
|
|
871
918
|
}
|
|
872
919
|
this.sql.exec(
|
|
873
|
-
"UPDATE records SET
|
|
920
|
+
"UPDATE records SET _data = ? WHERE _id = ? AND _type = ?",
|
|
874
921
|
JSON.stringify(merged),
|
|
875
922
|
id,
|
|
876
923
|
modelName
|
|
@@ -878,14 +925,14 @@ var DurableObjectEngine = class extends JsonSchemaEngine {
|
|
|
878
925
|
if (stringSets) {
|
|
879
926
|
for (const [fieldName, values] of Object.entries(stringSets)) {
|
|
880
927
|
this.sql.exec(
|
|
881
|
-
"DELETE FROM stringset_index WHERE
|
|
928
|
+
"DELETE FROM stringset_index WHERE _record_id = ? AND _type = ? AND field = ?",
|
|
882
929
|
id,
|
|
883
930
|
modelName,
|
|
884
931
|
fieldName
|
|
885
932
|
);
|
|
886
933
|
for (const value of values) {
|
|
887
934
|
this.sql.exec(
|
|
888
|
-
"INSERT OR IGNORE INTO stringset_index (
|
|
935
|
+
"INSERT OR IGNORE INTO stringset_index (_record_id, _type, field, value) VALUES (?, ?, ?, ?)",
|
|
889
936
|
id,
|
|
890
937
|
modelName,
|
|
891
938
|
fieldName,
|
|
@@ -916,11 +963,11 @@ var DurableObjectEngine = class extends JsonSchemaEngine {
|
|
|
916
963
|
*/
|
|
917
964
|
incrementFieldsRaw(modelName, id, fields) {
|
|
918
965
|
const rows = this.execSqlSync(
|
|
919
|
-
"SELECT
|
|
966
|
+
"SELECT _data FROM records WHERE _id = ? AND _type = ?",
|
|
920
967
|
[id, modelName]
|
|
921
968
|
);
|
|
922
969
|
if (rows.length === 0) return null;
|
|
923
|
-
const data = JSON.parse(rows[0].
|
|
970
|
+
const data = JSON.parse(rows[0]._data);
|
|
924
971
|
const newValues = {};
|
|
925
972
|
for (const [field, delta] of Object.entries(fields)) {
|
|
926
973
|
const current = typeof data[field] === "number" ? data[field] : 0;
|
|
@@ -928,7 +975,7 @@ var DurableObjectEngine = class extends JsonSchemaEngine {
|
|
|
928
975
|
newValues[field] = data[field];
|
|
929
976
|
}
|
|
930
977
|
this.sql.exec(
|
|
931
|
-
"UPDATE records SET
|
|
978
|
+
"UPDATE records SET _data = ? WHERE _id = ? AND _type = ?",
|
|
932
979
|
JSON.stringify(data),
|
|
933
980
|
id,
|
|
934
981
|
modelName
|
|
@@ -940,9 +987,9 @@ var DurableObjectEngine = class extends JsonSchemaEngine {
|
|
|
940
987
|
*/
|
|
941
988
|
async deleteWithStringSets(modelName, id) {
|
|
942
989
|
this.storage.transactionSync(() => {
|
|
943
|
-
this.sql.exec("DELETE FROM records WHERE
|
|
990
|
+
this.sql.exec("DELETE FROM records WHERE _type = ? AND _id = ?", modelName, id);
|
|
944
991
|
this.sql.exec(
|
|
945
|
-
"DELETE FROM stringset_index WHERE
|
|
992
|
+
"DELETE FROM stringset_index WHERE _type = ? AND _record_id = ?",
|
|
946
993
|
modelName,
|
|
947
994
|
id
|
|
948
995
|
);
|
|
@@ -954,7 +1001,7 @@ var DurableObjectEngine = class extends JsonSchemaEngine {
|
|
|
954
1001
|
*/
|
|
955
1002
|
trackModelFields(modelName, data) {
|
|
956
1003
|
for (const [key, value] of Object.entries(data)) {
|
|
957
|
-
if (key === "id" ||
|
|
1004
|
+
if (key === "id" || value === null || value === void 0) {
|
|
958
1005
|
continue;
|
|
959
1006
|
}
|
|
960
1007
|
let inferredType;
|
|
@@ -1277,18 +1324,20 @@ var JsonQueryTranslator = class {
|
|
|
1277
1324
|
}
|
|
1278
1325
|
/**
|
|
1279
1326
|
* Get SQL expression for a field.
|
|
1280
|
-
* System fields (id, type)
|
|
1327
|
+
* System fields (id, type) map to internal columns (_id, _type);
|
|
1328
|
+
* others use json_extract(_data, ...).
|
|
1281
1329
|
* When no schema is provided, any field is accepted (schemaless mode).
|
|
1282
1330
|
*/
|
|
1283
1331
|
getFieldSql(fieldName) {
|
|
1284
1332
|
if (!this.fieldSqlCache.has(fieldName)) {
|
|
1285
1333
|
if (SYSTEM_FIELDS.has(fieldName)) {
|
|
1286
|
-
|
|
1334
|
+
const internalName = `_${fieldName}`;
|
|
1335
|
+
this.fieldSqlCache.set(fieldName, quoteIdentifier(internalName));
|
|
1287
1336
|
} else {
|
|
1288
1337
|
assertValidIdentifier(fieldName, "query field");
|
|
1289
1338
|
this.fieldSqlCache.set(
|
|
1290
1339
|
fieldName,
|
|
1291
|
-
`json_extract(
|
|
1340
|
+
`json_extract(_data, '$.${fieldName}')`
|
|
1292
1341
|
);
|
|
1293
1342
|
}
|
|
1294
1343
|
}
|
|
@@ -1315,7 +1364,7 @@ var JsonQueryTranslator = class {
|
|
|
1315
1364
|
const selectClause = this.buildSelectClause(options?.projection);
|
|
1316
1365
|
let sql = `SELECT ${selectClause} FROM records`;
|
|
1317
1366
|
const params = [];
|
|
1318
|
-
const conditions = [`${quoteIdentifier("
|
|
1367
|
+
const conditions = [`${quoteIdentifier("_type")} = ?`];
|
|
1319
1368
|
params.push(this.modelName);
|
|
1320
1369
|
if (whereClause.sql) {
|
|
1321
1370
|
conditions.push(whereClause.sql);
|
|
@@ -1351,7 +1400,7 @@ var JsonQueryTranslator = class {
|
|
|
1351
1400
|
*/
|
|
1352
1401
|
translateCount(filter, options) {
|
|
1353
1402
|
const whereClause = this.translateFilter(filter);
|
|
1354
|
-
const conditions = [`${quoteIdentifier("
|
|
1403
|
+
const conditions = [`${quoteIdentifier("_type")} = ?`];
|
|
1355
1404
|
const params = [this.modelName];
|
|
1356
1405
|
if (whereClause.sql) {
|
|
1357
1406
|
conditions.push(whereClause.sql);
|
|
@@ -1536,7 +1585,7 @@ var JsonQueryTranslator = class {
|
|
|
1536
1585
|
}
|
|
1537
1586
|
}
|
|
1538
1587
|
if (fieldType === "stringset") {
|
|
1539
|
-
const likeSql = `EXISTS (SELECT 1 FROM stringset_index WHERE stringset_index.
|
|
1588
|
+
const likeSql = `EXISTS (SELECT 1 FROM stringset_index WHERE stringset_index._type = ? AND stringset_index.field = ? AND stringset_index._record_id = records._id AND stringset_index.value LIKE ? ESCAPE '\\' COLLATE NOCASE)`;
|
|
1540
1589
|
conditions.push(likeSql);
|
|
1541
1590
|
params.push(this.modelName, fieldName, pattern);
|
|
1542
1591
|
} else {
|
|
@@ -1682,10 +1731,10 @@ var JsonQueryTranslator = class {
|
|
|
1682
1731
|
return value ? { sql: `${fieldSql} IS NOT NULL`, params: [] } : { sql: `${fieldSql} IS NULL`, params: [] };
|
|
1683
1732
|
} else {
|
|
1684
1733
|
return value ? {
|
|
1685
|
-
sql: `json_type(
|
|
1734
|
+
sql: `json_type(_data, '$.${fieldName}') IS NOT NULL`,
|
|
1686
1735
|
params: []
|
|
1687
1736
|
} : {
|
|
1688
|
-
sql: `json_type(
|
|
1737
|
+
sql: `json_type(_data, '$.${fieldName}') IS NULL`,
|
|
1689
1738
|
params: []
|
|
1690
1739
|
};
|
|
1691
1740
|
}
|
|
@@ -1707,7 +1756,7 @@ var JsonQueryTranslator = class {
|
|
|
1707
1756
|
);
|
|
1708
1757
|
}
|
|
1709
1758
|
return {
|
|
1710
|
-
sql: `EXISTS (SELECT 1 FROM stringset_index WHERE stringset_index.
|
|
1759
|
+
sql: `EXISTS (SELECT 1 FROM stringset_index WHERE stringset_index._type = ? AND stringset_index.field = ? AND stringset_index._record_id = records._id AND stringset_index.value = ?)`,
|
|
1711
1760
|
params: [this.modelName, fieldName, value]
|
|
1712
1761
|
};
|
|
1713
1762
|
default:
|
|
@@ -1725,7 +1774,7 @@ var JsonQueryTranslator = class {
|
|
|
1725
1774
|
*/
|
|
1726
1775
|
buildSelectClause(projection) {
|
|
1727
1776
|
if (!projection || Object.keys(projection).length === 0) {
|
|
1728
|
-
return "
|
|
1777
|
+
return "_id, _type, _data";
|
|
1729
1778
|
}
|
|
1730
1779
|
const includeFields = [];
|
|
1731
1780
|
const excludeFields = [];
|
|
@@ -1751,18 +1800,18 @@ var JsonQueryTranslator = class {
|
|
|
1751
1800
|
if (!includeFields.includes("id")) {
|
|
1752
1801
|
includeFields.unshift("id");
|
|
1753
1802
|
}
|
|
1754
|
-
const selectParts = ["
|
|
1803
|
+
const selectParts = ["_id", "_type"];
|
|
1755
1804
|
for (const field of includeFields) {
|
|
1756
1805
|
if (field === "id" || field === "type") continue;
|
|
1757
1806
|
selectParts.push(
|
|
1758
|
-
`json_extract(
|
|
1807
|
+
`json_extract(_data, '$.${field}') AS ${quoteIdentifier(field)}`
|
|
1759
1808
|
);
|
|
1760
1809
|
}
|
|
1761
1810
|
return selectParts.join(", ");
|
|
1762
1811
|
} else if (hasExcludes) {
|
|
1763
|
-
return "
|
|
1812
|
+
return "_id, _type, _data";
|
|
1764
1813
|
}
|
|
1765
|
-
return "
|
|
1814
|
+
return "_id, _type, _data";
|
|
1766
1815
|
}
|
|
1767
1816
|
/**
|
|
1768
1817
|
* Build LIMIT clause
|
|
@@ -2104,11 +2153,9 @@ function createDatabaseDO(config = {}) {
|
|
|
2104
2153
|
/** @internal — Check for reserved field names in record data */
|
|
2105
2154
|
_checkReservedFields(data) {
|
|
2106
2155
|
for (const key of Object.keys(data)) {
|
|
2107
|
-
if (key === "
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
if (key.startsWith("_meta")) {
|
|
2111
|
-
return `Field '${key}' is reserved (fields starting with '_meta' are internal) and cannot be used in record data`;
|
|
2156
|
+
if (key === "id") continue;
|
|
2157
|
+
if (key.startsWith("_")) {
|
|
2158
|
+
return `Field '${key}' is reserved (fields starting with '_' are internal)`;
|
|
2112
2159
|
}
|
|
2113
2160
|
}
|
|
2114
2161
|
return null;
|
|
@@ -2216,11 +2263,11 @@ function createDatabaseDO(config = {}) {
|
|
|
2216
2263
|
}
|
|
2217
2264
|
}
|
|
2218
2265
|
const existing = this._engine.execSqlSync(
|
|
2219
|
-
"SELECT
|
|
2266
|
+
"SELECT _data FROM records WHERE _id = ? AND _type = ?",
|
|
2220
2267
|
[id, modelName]
|
|
2221
2268
|
);
|
|
2222
2269
|
if (existing.length > 0) {
|
|
2223
|
-
const existingData = JSON.parse(existing[0].
|
|
2270
|
+
const existingData = JSON.parse(existing[0]._data);
|
|
2224
2271
|
const mergedData = { ...existingData, ...data };
|
|
2225
2272
|
const violation = this._engine.checkUniqueConstraints(
|
|
2226
2273
|
modelName,
|
|
@@ -2252,7 +2299,7 @@ function createDatabaseDO(config = {}) {
|
|
|
2252
2299
|
if (hooks?.beforeDelete) {
|
|
2253
2300
|
let record = null;
|
|
2254
2301
|
const rows = this._engine.execSqlSync(
|
|
2255
|
-
"SELECT
|
|
2302
|
+
"SELECT _id, _type, _data FROM records WHERE _id = ? AND _type = ?",
|
|
2256
2303
|
[id, modelName]
|
|
2257
2304
|
);
|
|
2258
2305
|
if (rows.length > 0) {
|
|
@@ -2326,7 +2373,7 @@ function createDatabaseDO(config = {}) {
|
|
|
2326
2373
|
if (hooks?.beforeDelete) {
|
|
2327
2374
|
let record = null;
|
|
2328
2375
|
const rows = this._engine.execSqlSync(
|
|
2329
|
-
"SELECT
|
|
2376
|
+
"SELECT _id, _type, _data FROM records WHERE _id = ? AND _type = ?",
|
|
2330
2377
|
[op.id, op.modelName]
|
|
2331
2378
|
);
|
|
2332
2379
|
if (rows.length > 0) {
|
|
@@ -2412,30 +2459,30 @@ function createDatabaseDO(config = {}) {
|
|
|
2412
2459
|
}
|
|
2413
2460
|
}
|
|
2414
2461
|
if (op.stringSets && Object.keys(op.stringSets).length > 0) {
|
|
2415
|
-
const { id: _id,
|
|
2462
|
+
const { id: _id, ...fieldsForJson } = op.data;
|
|
2416
2463
|
const jsonFields = { ...fieldsForJson, ...op.stringSets };
|
|
2417
2464
|
const dataJson = JSON.stringify(jsonFields);
|
|
2418
2465
|
this._engine.execSqlSync(
|
|
2419
|
-
"INSERT OR REPLACE INTO records (
|
|
2466
|
+
"INSERT OR REPLACE INTO records (_id, _type, _data) VALUES (?, ?, ?)",
|
|
2420
2467
|
[op.id, op.modelName, dataJson]
|
|
2421
2468
|
);
|
|
2422
2469
|
this._engine.execSqlSync(
|
|
2423
|
-
"DELETE FROM stringset_index WHERE
|
|
2470
|
+
"DELETE FROM stringset_index WHERE _type = ? AND _record_id = ?",
|
|
2424
2471
|
[op.modelName, op.id]
|
|
2425
2472
|
);
|
|
2426
2473
|
for (const [fieldName, values] of Object.entries(op.stringSets)) {
|
|
2427
2474
|
for (const value of values) {
|
|
2428
2475
|
this._engine.execSqlSync(
|
|
2429
|
-
"INSERT OR IGNORE INTO stringset_index (
|
|
2476
|
+
"INSERT OR IGNORE INTO stringset_index (_record_id, _type, field, value) VALUES (?, ?, ?, ?)",
|
|
2430
2477
|
[op.id, op.modelName, fieldName, value]
|
|
2431
2478
|
);
|
|
2432
2479
|
}
|
|
2433
2480
|
}
|
|
2434
2481
|
} else {
|
|
2435
|
-
const { id: _id,
|
|
2482
|
+
const { id: _id, ...fieldsForJson } = op.data;
|
|
2436
2483
|
const dataJson = JSON.stringify(fieldsForJson);
|
|
2437
2484
|
this._engine.execSqlSync(
|
|
2438
|
-
"INSERT OR REPLACE INTO records (
|
|
2485
|
+
"INSERT OR REPLACE INTO records (_id, _type, _data) VALUES (?, ?, ?)",
|
|
2439
2486
|
[op.id, op.modelName, dataJson]
|
|
2440
2487
|
);
|
|
2441
2488
|
}
|
|
@@ -2452,11 +2499,11 @@ function createDatabaseDO(config = {}) {
|
|
|
2452
2499
|
}
|
|
2453
2500
|
if (op.data) {
|
|
2454
2501
|
const existing = this._engine.execSqlSync(
|
|
2455
|
-
"SELECT
|
|
2502
|
+
"SELECT _data FROM records WHERE _id = ? AND _type = ?",
|
|
2456
2503
|
[op.id, op.modelName]
|
|
2457
2504
|
);
|
|
2458
2505
|
if (existing.length > 0) {
|
|
2459
|
-
const existingData = JSON.parse(existing[0].
|
|
2506
|
+
const existingData = JSON.parse(existing[0]._data);
|
|
2460
2507
|
const mergedData = { ...existingData, ...op.data };
|
|
2461
2508
|
const violation = this._engine.checkUniqueConstraints(
|
|
2462
2509
|
op.modelName,
|
|
@@ -2491,11 +2538,11 @@ function createDatabaseDO(config = {}) {
|
|
|
2491
2538
|
}
|
|
2492
2539
|
} else if (op.op === "delete") {
|
|
2493
2540
|
this._engine.execSqlSync(
|
|
2494
|
-
"DELETE FROM records WHERE
|
|
2541
|
+
"DELETE FROM records WHERE _id = ? AND _type = ?",
|
|
2495
2542
|
[op.id, op.modelName]
|
|
2496
2543
|
);
|
|
2497
2544
|
this._engine.execSqlSync(
|
|
2498
|
-
"DELETE FROM stringset_index WHERE
|
|
2545
|
+
"DELETE FROM stringset_index WHERE _record_id = ? AND _type = ?",
|
|
2499
2546
|
[op.id, op.modelName]
|
|
2500
2547
|
);
|
|
2501
2548
|
results.push({ success: true, id: op.id });
|
|
@@ -2707,7 +2754,7 @@ function createDatabaseDO(config = {}) {
|
|
|
2707
2754
|
for (const field of regularGroupBy) {
|
|
2708
2755
|
assertValidIdentifier(field, "aggregation groupBy field");
|
|
2709
2756
|
const ssCheck = this._engine.execSqlSync(
|
|
2710
|
-
"SELECT 1 FROM stringset_index WHERE
|
|
2757
|
+
"SELECT 1 FROM stringset_index WHERE _type = ? AND field = ? LIMIT 1",
|
|
2711
2758
|
[modelName, field]
|
|
2712
2759
|
);
|
|
2713
2760
|
if (ssCheck.length > 0) {
|
|
@@ -2783,9 +2830,9 @@ function createDatabaseDO(config = {}) {
|
|
|
2783
2830
|
}
|
|
2784
2831
|
}
|
|
2785
2832
|
let sql = `SELECT ${selectParts.join(", ")} FROM stringset_index`;
|
|
2786
|
-
sql += ` INNER JOIN records ON stringset_index.
|
|
2833
|
+
sql += ` INNER JOIN records ON stringset_index._record_id = records._id AND records._type = stringset_index._type`;
|
|
2787
2834
|
const conditions = [
|
|
2788
|
-
"stringset_index.
|
|
2835
|
+
"stringset_index._type = ?",
|
|
2789
2836
|
"stringset_index.field = ?"
|
|
2790
2837
|
];
|
|
2791
2838
|
const params = [modelName, facetField];
|
|
@@ -2822,9 +2869,9 @@ function createDatabaseDO(config = {}) {
|
|
|
2822
2869
|
const m = stringSetMemberships[i];
|
|
2823
2870
|
const alias = `ss_${m.field}_${i}`;
|
|
2824
2871
|
selectParts.push(
|
|
2825
|
-
`CASE WHEN ${alias}.
|
|
2872
|
+
`CASE WHEN ${alias}._record_id IS NOT NULL THEN 'true' ELSE 'false' END AS "${alias}"`
|
|
2826
2873
|
);
|
|
2827
|
-
groupByParts.push(`CASE WHEN ${alias}.
|
|
2874
|
+
groupByParts.push(`CASE WHEN ${alias}._record_id IS NOT NULL THEN 'true' ELSE 'false' END`);
|
|
2828
2875
|
aliasMap.push({ alias, field: m.field, contains: m.contains });
|
|
2829
2876
|
}
|
|
2830
2877
|
for (const op of aggOptions.operations) {
|
|
@@ -2850,13 +2897,13 @@ function createDatabaseDO(config = {}) {
|
|
|
2850
2897
|
const joinParams = [];
|
|
2851
2898
|
for (const entry of aliasMap) {
|
|
2852
2899
|
sql += ` LEFT JOIN stringset_index AS ${entry.alias}`;
|
|
2853
|
-
sql += ` ON ${entry.alias}.
|
|
2854
|
-
sql += ` AND ${entry.alias}.
|
|
2900
|
+
sql += ` ON ${entry.alias}._record_id = records._id`;
|
|
2901
|
+
sql += ` AND ${entry.alias}._type = records._type`;
|
|
2855
2902
|
sql += ` AND ${entry.alias}.field = ?`;
|
|
2856
2903
|
sql += ` AND ${entry.alias}.value = ?`;
|
|
2857
2904
|
joinParams.push(entry.field, entry.contains);
|
|
2858
2905
|
}
|
|
2859
|
-
const conditions = ["records.
|
|
2906
|
+
const conditions = ["records._type = ?"];
|
|
2860
2907
|
const params = [...joinParams, modelName];
|
|
2861
2908
|
if (filter && Object.keys(filter).length > 0) {
|
|
2862
2909
|
const whereClause = translator.translateFilter(filter);
|
|
@@ -3114,7 +3161,7 @@ function createDatabaseDO(config = {}) {
|
|
|
3114
3161
|
);
|
|
3115
3162
|
}
|
|
3116
3163
|
const check = this._engine.execSqlSync(
|
|
3117
|
-
`SELECT 1 FROM records WHERE
|
|
3164
|
+
`SELECT 1 FROM records WHERE _type = ? AND json_type(_data, '$.${field}') IS NOT NULL AND json_type(_data, '$.${field}') != 'array' LIMIT 1`,
|
|
3118
3165
|
[modelName]
|
|
3119
3166
|
);
|
|
3120
3167
|
if (check.length > 0) {
|
|
@@ -3138,7 +3185,7 @@ function createDatabaseDO(config = {}) {
|
|
|
3138
3185
|
includeDocId: false
|
|
3139
3186
|
});
|
|
3140
3187
|
const whereClause = translator.translateFilter(condition);
|
|
3141
|
-
let sql = "SELECT 1 FROM records WHERE
|
|
3188
|
+
let sql = "SELECT 1 FROM records WHERE _id = ? AND _type = ?";
|
|
3142
3189
|
const params = [id, modelName];
|
|
3143
3190
|
if (whereClause.sql) {
|
|
3144
3191
|
sql += ` AND ${whereClause.sql}`;
|
|
@@ -3165,7 +3212,7 @@ function createDatabaseDO(config = {}) {
|
|
|
3165
3212
|
/** @internal — List all known model names from records and _model_fields */
|
|
3166
3213
|
_handleModelList() {
|
|
3167
3214
|
const fromRecords = this._engine.execSqlSync(
|
|
3168
|
-
"SELECT DISTINCT
|
|
3215
|
+
"SELECT DISTINCT _type FROM records ORDER BY _type"
|
|
3169
3216
|
);
|
|
3170
3217
|
const fromFields = this._engine.execSqlSync(
|
|
3171
3218
|
"SELECT DISTINCT model_name FROM _model_fields ORDER BY model_name"
|
|
@@ -3174,7 +3221,7 @@ function createDatabaseDO(config = {}) {
|
|
|
3174
3221
|
"SELECT DISTINCT model_name FROM _indexes ORDER BY model_name"
|
|
3175
3222
|
);
|
|
3176
3223
|
const modelSet = /* @__PURE__ */ new Set();
|
|
3177
|
-
for (const row of fromRecords) modelSet.add(row.
|
|
3224
|
+
for (const row of fromRecords) modelSet.add(row._type);
|
|
3178
3225
|
for (const row of fromFields) modelSet.add(row.model_name);
|
|
3179
3226
|
for (const row of fromIndexes) modelSet.add(row.model_name);
|
|
3180
3227
|
return Response.json({ models: Array.from(modelSet).sort() });
|
|
@@ -3186,16 +3233,16 @@ function createDatabaseDO(config = {}) {
|
|
|
3186
3233
|
}
|
|
3187
3234
|
/** @internal */
|
|
3188
3235
|
_parseRow(row) {
|
|
3189
|
-
const {
|
|
3236
|
+
const { _id, _type, _data, ...rest } = row;
|
|
3190
3237
|
let parsed = {};
|
|
3191
|
-
if (
|
|
3238
|
+
if (_data) {
|
|
3192
3239
|
try {
|
|
3193
|
-
parsed = JSON.parse(
|
|
3240
|
+
parsed = JSON.parse(_data);
|
|
3194
3241
|
} catch (e) {
|
|
3195
|
-
console.warn("Failed to parse
|
|
3242
|
+
console.warn("Failed to parse _data:", e);
|
|
3196
3243
|
}
|
|
3197
3244
|
}
|
|
3198
|
-
return { id, type, ...parsed, ...rest };
|
|
3245
|
+
return { id: _id, type: _type, ...parsed, ...rest };
|
|
3199
3246
|
}
|
|
3200
3247
|
// ─── Include (Related Data Loading) ──────────────────────────────
|
|
3201
3248
|
/** @internal — Validate an IncludeSpec, returning an error message or null */
|
|
@@ -3425,12 +3472,12 @@ function createDatabaseDO(config = {}) {
|
|
|
3425
3472
|
_resolveHasManyWithLimit(spec, parentValues) {
|
|
3426
3473
|
const foreignKey = spec.foreignKey;
|
|
3427
3474
|
assertValidIdentifier(foreignKey, "include foreignKey");
|
|
3428
|
-
let sortClause = `"
|
|
3475
|
+
let sortClause = `"_id" ASC`;
|
|
3429
3476
|
if (spec.sort) {
|
|
3430
3477
|
const parts = [];
|
|
3431
3478
|
for (const [field, dir] of Object.entries(spec.sort)) {
|
|
3432
3479
|
assertValidIdentifier(field, "include sort field");
|
|
3433
|
-
const fieldSql = field === "id"
|
|
3480
|
+
const fieldSql = field === "id" ? `"_id"` : field === "type" ? `"_type"` : `json_extract(_data, '$.${field}')`;
|
|
3434
3481
|
parts.push(`${fieldSql} ${dir === -1 ? "DESC" : "ASC"}`);
|
|
3435
3482
|
}
|
|
3436
3483
|
sortClause = parts.join(", ");
|
|
@@ -3454,15 +3501,15 @@ function createDatabaseDO(config = {}) {
|
|
|
3454
3501
|
const chunk = parentValues.slice(i, i + chunkSize);
|
|
3455
3502
|
const inPlaceholders = chunk.map(() => "?").join(", ");
|
|
3456
3503
|
const sql = `
|
|
3457
|
-
SELECT "
|
|
3458
|
-
SELECT "
|
|
3504
|
+
SELECT "_id", "_type", _data FROM (
|
|
3505
|
+
SELECT "_id", "_type", _data,
|
|
3459
3506
|
ROW_NUMBER() OVER (
|
|
3460
|
-
PARTITION BY json_extract(
|
|
3507
|
+
PARTITION BY json_extract(_data, '$.${foreignKey}')
|
|
3461
3508
|
ORDER BY ${sortClause}
|
|
3462
3509
|
) as _rn
|
|
3463
3510
|
FROM records
|
|
3464
|
-
WHERE "
|
|
3465
|
-
AND json_extract(
|
|
3511
|
+
WHERE "_type" = ?
|
|
3512
|
+
AND json_extract(_data, '$.${foreignKey}') IN (${inPlaceholders})
|
|
3466
3513
|
${filterClause}
|
|
3467
3514
|
) WHERE _rn <= ?
|
|
3468
3515
|
`;
|