js-bao 0.12.0-alpha.2 → 0.12.0-alpha.4
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 +57 -19
- package/dist/browser.cjs +109 -129
- package/dist/browser.d.cts +52 -43
- package/dist/browser.d.ts +52 -43
- package/dist/browser.js +109 -129
- package/dist/client.cjs +8 -0
- package/dist/client.d.cts +52 -33
- package/dist/client.d.ts +52 -33
- package/dist/client.js +8 -0
- package/dist/cloudflare-do.cjs +108 -92
- package/dist/cloudflare-do.d.cts +87 -63
- package/dist/cloudflare-do.d.ts +87 -63
- package/dist/cloudflare-do.js +108 -92
- package/dist/cloudflare.cjs +8 -0
- package/dist/cloudflare.d.cts +52 -33
- package/dist/cloudflare.d.ts +52 -33
- package/dist/cloudflare.js +8 -0
- package/dist/codegen-v2.cjs +17 -3
- package/dist/codegen.cjs +9 -39
- package/dist/format2-worker.js +1710 -187
- package/dist/index.cjs +1497 -155
- package/dist/index.d.cts +1029 -101
- package/dist/index.d.ts +1029 -101
- package/dist/index.js +1455 -153
- package/dist/node.cjs +124 -133
- package/dist/node.d.cts +52 -43
- package/dist/node.d.ts +52 -43
- package/dist/node.js +124 -132
- package/dist/record-log.cjs +1392 -0
- package/dist/record-log.d.cts +920 -0
- package/dist/record-log.d.ts +920 -0
- package/dist/record-log.js +1369 -0
- package/dist/sqlite3.wasm +0 -0
- package/package.json +10 -4
package/README.md
CHANGED
|
@@ -1023,6 +1023,12 @@ const electronicsCount = await Product.count({
|
|
|
1023
1023
|
|
|
1024
1024
|
Perform complex data analysis with grouping, statistical operations, and faceting.
|
|
1025
1025
|
|
|
1026
|
+
`aggregate` answers one row per group: the `groupBy` values under `group`, as
|
|
1027
|
+
stored (`null` for a missing field), and each operation's value beside it under
|
|
1028
|
+
`count` or `<type>_<field>` — even when it is the only operation. Rows come back
|
|
1029
|
+
in `sort` order; no matching records gives `[]`. Two `groupBy` entries that
|
|
1030
|
+
would share a key in `group` throw `Ambiguous aggregation group keys`.
|
|
1031
|
+
|
|
1026
1032
|
### Basic Aggregation
|
|
1027
1033
|
|
|
1028
1034
|
```typescript
|
|
@@ -1031,7 +1037,11 @@ const categoryCounts = await Product.aggregate({
|
|
|
1031
1037
|
groupBy: ["category"],
|
|
1032
1038
|
operations: [{ type: "count" }],
|
|
1033
1039
|
});
|
|
1034
|
-
// Result:
|
|
1040
|
+
// Result: [
|
|
1041
|
+
// { group: { category: "Electronics" }, count: 25 },
|
|
1042
|
+
// { group: { category: "Books" }, count: 18 },
|
|
1043
|
+
// { group: { category: "Clothing" }, count: 12 }
|
|
1044
|
+
// ]
|
|
1035
1045
|
|
|
1036
1046
|
// Multiple statistical operations
|
|
1037
1047
|
const categoryStats = await Product.aggregate({
|
|
@@ -1044,8 +1054,9 @@ const categoryStats = await Product.aggregate({
|
|
|
1044
1054
|
{ type: "max", field: "price" },
|
|
1045
1055
|
],
|
|
1046
1056
|
});
|
|
1047
|
-
// Result:
|
|
1048
|
-
//
|
|
1057
|
+
// Result: [
|
|
1058
|
+
// {
|
|
1059
|
+
// group: { category: "Electronics" },
|
|
1049
1060
|
// count: 25,
|
|
1050
1061
|
// avg_price: 299.99,
|
|
1051
1062
|
// sum_price: 7499.75,
|
|
@@ -1053,7 +1064,7 @@ const categoryStats = await Product.aggregate({
|
|
|
1053
1064
|
// max_price: 1299.99
|
|
1054
1065
|
// },
|
|
1055
1066
|
// ...
|
|
1056
|
-
//
|
|
1067
|
+
// ]
|
|
1057
1068
|
```
|
|
1058
1069
|
|
|
1059
1070
|
### Multi-Dimensional Grouping
|
|
@@ -1064,15 +1075,11 @@ const salesData = await Product.aggregate({
|
|
|
1064
1075
|
groupBy: ["category", "brand"],
|
|
1065
1076
|
operations: [{ type: "count" }, { type: "sum", field: "revenue" }],
|
|
1066
1077
|
});
|
|
1067
|
-
// Result:
|
|
1068
|
-
// "Electronics":
|
|
1069
|
-
//
|
|
1070
|
-
//
|
|
1071
|
-
//
|
|
1072
|
-
// "Books": {
|
|
1073
|
-
// "Penguin": { count: 25, sum_revenue: 450 }
|
|
1074
|
-
// }
|
|
1075
|
-
// }
|
|
1078
|
+
// Result: [
|
|
1079
|
+
// { group: { category: "Electronics", brand: "Apple" }, count: 12, sum_revenue: 15000 },
|
|
1080
|
+
// { group: { category: "Electronics", brand: "Samsung" }, count: 8, sum_revenue: 9500 },
|
|
1081
|
+
// { group: { category: "Books", brand: "Penguin" }, count: 25, sum_revenue: 450 }
|
|
1082
|
+
// ]
|
|
1076
1083
|
```
|
|
1077
1084
|
|
|
1078
1085
|
### StringSet Aggregation
|
|
@@ -1105,14 +1112,21 @@ const tagCounts = await Article.aggregate({
|
|
|
1105
1112
|
groupBy: ["tags"], // StringSet faceting
|
|
1106
1113
|
operations: [{ type: "count" }],
|
|
1107
1114
|
});
|
|
1108
|
-
// Result:
|
|
1115
|
+
// Result: [
|
|
1116
|
+
// { group: { tags: "javascript" }, count: 45 },
|
|
1117
|
+
// { group: { tags: "react" }, count: 32 },
|
|
1118
|
+
// { group: { tags: "tutorial" }, count: 28 }
|
|
1119
|
+
// ]
|
|
1109
1120
|
|
|
1110
1121
|
// Membership-based grouping (articles that have specific tag vs don't)
|
|
1111
1122
|
const urgentCounts = await Article.aggregate({
|
|
1112
1123
|
groupBy: [{ field: "tags", contains: "urgent" }],
|
|
1113
1124
|
operations: [{ type: "count" }],
|
|
1114
1125
|
});
|
|
1115
|
-
// Result:
|
|
1126
|
+
// Result: [
|
|
1127
|
+
// { group: { has_tags_urgent: "true" }, count: 5 },
|
|
1128
|
+
// { group: { has_tags_urgent: "false" }, count: 120 }
|
|
1129
|
+
// ]
|
|
1116
1130
|
|
|
1117
1131
|
// Complex aggregation with filtering and sorting
|
|
1118
1132
|
const topTags = await Article.aggregate({
|
|
@@ -1254,7 +1268,10 @@ const tagStats = await Article.aggregate({
|
|
|
1254
1268
|
groupBy: [{ field: "tags", contains: "tutorial" }],
|
|
1255
1269
|
operations: [{ type: "count" }],
|
|
1256
1270
|
});
|
|
1257
|
-
// Result:
|
|
1271
|
+
// Result: [
|
|
1272
|
+
// { group: { has_tags_tutorial: "true" }, count: 25 },
|
|
1273
|
+
// { group: { has_tags_tutorial: "false" }, count: 75 }
|
|
1274
|
+
// ]
|
|
1258
1275
|
```
|
|
1259
1276
|
|
|
1260
1277
|
## Database Engine Specifics
|
|
@@ -1556,9 +1573,30 @@ See the `examples/` directory for complete working examples:
|
|
|
1556
1573
|
|
|
1557
1574
|
## Release notes
|
|
1558
1575
|
|
|
1559
|
-
Changes
|
|
1560
|
-
|
|
1561
|
-
|
|
1576
|
+
Changes a consumer of this package can observe, including the on-disk
|
|
1577
|
+
behaviour of `js-bao/cloudflare/do` (`DurableObjectEngine`). Newest first.
|
|
1578
|
+
|
|
1579
|
+
- **Breaking: `createModelClass` is removed** (#4285). It is no longer exported
|
|
1580
|
+
from `js-bao` (or re-exported from `js-bao-wss-client`). Define a model as
|
|
1581
|
+
`class X extends BaseModel {}` and attach its schema with
|
|
1582
|
+
`attachAndRegisterModel(X, schema)`, as [Defining Models](#defining-models-schema-first-no-decorators)
|
|
1583
|
+
shows; instance methods, getters and statics go on the class body. The v1
|
|
1584
|
+
codegen (`js-bao-codegen`) no longer recognises a `createModelClass(...)`
|
|
1585
|
+
call as a model definition; it reads the class-based pattern as before.
|
|
1586
|
+
- **New stores get a keyed StringSet table** (#3881). A store the engine
|
|
1587
|
+
creates declares `stringset_index` as `PRIMARY KEY (_type, _record_id, field,
|
|
1588
|
+
value) WITHOUT ROWID` with `idx_ss_type_field_value` as its one named index,
|
|
1589
|
+
so a StringSet value costs two rows written instead of three.
|
|
1590
|
+
`DURABLE_OBJECT_SCHEMA_OPTIONS.memberShape` is `"keyed"`, and
|
|
1591
|
+
`engine.memberTableShape` answers `"keyed"` for such a store. An existing
|
|
1592
|
+
store keeps its record-first or field-first table; all three shapes return
|
|
1593
|
+
the same results. On the platform, a plain document converts at its next
|
|
1594
|
+
projection rebuild; databases and large documents keep their shape.
|
|
1595
|
+
`rebuildMemberTable` now builds the keyed table. Rollback floor: a keyed table
|
|
1596
|
+
has no `rowid`, so once a keyed store exists do not roll back past the
|
|
1597
|
+
release that first returned StringSet values sorted by code point (it reads
|
|
1598
|
+
`ORDER BY value`); an older release fails on a keyed table with `no such
|
|
1599
|
+
column: rowid`.
|
|
1562
1600
|
- **New stores get a record-first StringSet table** (#3877). A store the
|
|
1563
1601
|
engine creates declares `stringset_index` with `UNIQUE(_type, _record_id,
|
|
1564
1602
|
field, value)` (record-first) instead of `UNIQUE(_type, field, _record_id,
|
package/dist/browser.cjs
CHANGED
|
@@ -8672,6 +8672,51 @@ var init_format2ClientWrite = __esm({
|
|
|
8672
8672
|
}
|
|
8673
8673
|
});
|
|
8674
8674
|
|
|
8675
|
+
// src/utils/aggregationRows.ts
|
|
8676
|
+
function aggregationGroupKey(level) {
|
|
8677
|
+
return typeof level === "string" ? level : `has_${level.field}_${level.contains}`;
|
|
8678
|
+
}
|
|
8679
|
+
function aggregationOperationKey(operation) {
|
|
8680
|
+
return operation.type === "count" ? "count" : `${operation.type}_${operation.field}`;
|
|
8681
|
+
}
|
|
8682
|
+
function positionalGroupAlias(index) {
|
|
8683
|
+
return `_g${index}`;
|
|
8684
|
+
}
|
|
8685
|
+
function ambiguousAggregationGroupKeysMessage(key) {
|
|
8686
|
+
return `Ambiguous aggregation group keys: '${key}' names more than one groupBy level`;
|
|
8687
|
+
}
|
|
8688
|
+
function findAmbiguousAggregationGroupKey(groupBy) {
|
|
8689
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8690
|
+
for (const level of groupBy) {
|
|
8691
|
+
const key = aggregationGroupKey(level);
|
|
8692
|
+
if (seen.has(key)) return key;
|
|
8693
|
+
seen.add(key);
|
|
8694
|
+
}
|
|
8695
|
+
return null;
|
|
8696
|
+
}
|
|
8697
|
+
function aggregationRow(sqlRow, groupBy, operations, readLevel) {
|
|
8698
|
+
const group = {};
|
|
8699
|
+
groupBy.forEach((level, index) => {
|
|
8700
|
+
Object.defineProperty(group, aggregationGroupKey(level), {
|
|
8701
|
+
value: readLevel(level, index),
|
|
8702
|
+
enumerable: true,
|
|
8703
|
+
writable: true,
|
|
8704
|
+
configurable: true
|
|
8705
|
+
});
|
|
8706
|
+
});
|
|
8707
|
+
const row = { group };
|
|
8708
|
+
for (const operation of operations) {
|
|
8709
|
+
const key = aggregationOperationKey(operation);
|
|
8710
|
+
row[key] = sqlRow[key] ?? null;
|
|
8711
|
+
}
|
|
8712
|
+
return row;
|
|
8713
|
+
}
|
|
8714
|
+
var init_aggregationRows = __esm({
|
|
8715
|
+
"src/utils/aggregationRows.ts"() {
|
|
8716
|
+
"use strict";
|
|
8717
|
+
}
|
|
8718
|
+
});
|
|
8719
|
+
|
|
8675
8720
|
// src/models/relationshipManager.ts
|
|
8676
8721
|
function warnIfOrderFieldNullable(modelClass, orderByField, relationshipDescription) {
|
|
8677
8722
|
if (ALWAYS_PRESENT_ORDER_FIELDS.has(orderByField)) return;
|
|
@@ -8957,7 +9002,7 @@ var init_ModelRegistry = __esm({
|
|
|
8957
9002
|
* A registry for ONE `initJsBao` instance (#3039).
|
|
8958
9003
|
*
|
|
8959
9004
|
* The global singleton stays the registration target for
|
|
8960
|
-
* `attachAndRegisterModel
|
|
9005
|
+
* `attachAndRegisterModel`; this is the per-instance
|
|
8961
9006
|
* view the ORM actually resolves schemas through, so two clients that
|
|
8962
9007
|
* register different models under the same name no longer overwrite each
|
|
8963
9008
|
* other's include translation and relationship validation.
|
|
@@ -9005,7 +9050,7 @@ var init_ModelRegistry = __esm({
|
|
|
9005
9050
|
const modelName = modelClass?.modelName ?? schema?.options?.name;
|
|
9006
9051
|
if (!modelName || !schema?.options?.name || !schema?.resolvedUniqueConstraints) {
|
|
9007
9052
|
throw new Error(
|
|
9008
|
-
`[initJsBao] Cannot register a model class with no name. Build it with defineModelSchema and attachAndRegisterModel
|
|
9053
|
+
`[initJsBao] Cannot register a model class with no name. Build it with defineModelSchema and attachAndRegisterModel so it carries a static modelName and a schema with resolvedUniqueConstraints.`
|
|
9009
9054
|
);
|
|
9010
9055
|
}
|
|
9011
9056
|
const existing = this.models.get(modelName);
|
|
@@ -9054,7 +9099,7 @@ var init_ModelRegistry = __esm({
|
|
|
9054
9099
|
registerModel(modelClass, options, fields) {
|
|
9055
9100
|
if (!options.name) {
|
|
9056
9101
|
throw new Error(
|
|
9057
|
-
`[ModelRegistry] Model class is missing a name in its options. Ensure the schema passed to defineModelSchema/
|
|
9102
|
+
`[ModelRegistry] Model class is missing a name in its options. Ensure the schema passed to defineModelSchema/attachAndRegisterModel includes a 'name' property.`
|
|
9058
9103
|
);
|
|
9059
9104
|
}
|
|
9060
9105
|
if (this.models.has(options.name)) {
|
|
@@ -9098,7 +9143,7 @@ var init_ModelRegistry = __esm({
|
|
|
9098
9143
|
}
|
|
9099
9144
|
return infos;
|
|
9100
9145
|
}
|
|
9101
|
-
// Helper to get model name from class (set by attachAndRegisterModel
|
|
9146
|
+
// Helper to get model name from class (set by attachAndRegisterModel)
|
|
9102
9147
|
getModelNameFromClass(modelClass) {
|
|
9103
9148
|
return modelClass.modelName;
|
|
9104
9149
|
}
|
|
@@ -9109,13 +9154,13 @@ var init_ModelRegistry = __esm({
|
|
|
9109
9154
|
const modelName = this.getModelNameFromClass(modelClass);
|
|
9110
9155
|
if (!modelName) {
|
|
9111
9156
|
console.warn(
|
|
9112
|
-
`[ModelRegistry] A model class provided to setExplicitModelsForSession does not have a static 'modelName' property or it's undefined. It will be ignored. Ensure attachAndRegisterModel
|
|
9157
|
+
`[ModelRegistry] A model class provided to setExplicitModelsForSession does not have a static 'modelName' property or it's undefined. It will be ignored. Ensure attachAndRegisterModel was called for this model.`
|
|
9113
9158
|
);
|
|
9114
9159
|
return;
|
|
9115
9160
|
}
|
|
9116
9161
|
if (!this.models.has(modelName) || this.models.get(modelName) !== modelClass) {
|
|
9117
9162
|
console.warn(
|
|
9118
|
-
`[ModelRegistry] Model class with name ${modelName} provided to setExplicitModelsForSession was not found in the global registry or does not match. It will be ignored. Ensure the model file is imported and attachAndRegisterModel
|
|
9163
|
+
`[ModelRegistry] Model class with name ${modelName} provided to setExplicitModelsForSession was not found in the global registry or does not match. It will be ignored. Ensure the model file is imported and attachAndRegisterModel has run.`
|
|
9119
9164
|
);
|
|
9120
9165
|
return;
|
|
9121
9166
|
}
|
|
@@ -9917,6 +9962,7 @@ var init_BaseModel = __esm({
|
|
|
9917
9962
|
init_format2OfflineWindow();
|
|
9918
9963
|
init_logger();
|
|
9919
9964
|
init_queryTableKey();
|
|
9965
|
+
init_aggregationRows();
|
|
9920
9966
|
FORMAT2_ROW_PAGE = 500;
|
|
9921
9967
|
UniqueConstraintViolationError = class _UniqueConstraintViolationError extends Error {
|
|
9922
9968
|
modelName;
|
|
@@ -13209,7 +13255,7 @@ var init_BaseModel = __esm({
|
|
|
13209
13255
|
/**
|
|
13210
13256
|
* Main aggregation API - performs grouping, faceting, and statistical operations
|
|
13211
13257
|
* @param options Aggregation configuration with groupBy, operations, filter, limit, and sort
|
|
13212
|
-
* @returns
|
|
13258
|
+
* @returns One row per group, `{ group, ...operationValues }`, in SQL order
|
|
13213
13259
|
*
|
|
13214
13260
|
* @example
|
|
13215
13261
|
* // Simple facet count
|
|
@@ -13217,7 +13263,7 @@ var init_BaseModel = __esm({
|
|
|
13217
13263
|
* groupBy: ['tags'],
|
|
13218
13264
|
* operations: [{ type: 'count' }]
|
|
13219
13265
|
* });
|
|
13220
|
-
* // Result: { red: 15,
|
|
13266
|
+
* // Result: [{ group: { tags: 'red' }, count: 15 }, { group: { tags: 'blue' }, count: 8 }]
|
|
13221
13267
|
*
|
|
13222
13268
|
* @example
|
|
13223
13269
|
* // Multi-dimensional grouping with multiple operations
|
|
@@ -13229,9 +13275,10 @@ var init_BaseModel = __esm({
|
|
|
13229
13275
|
* { type: 'avg', field: 'score' }
|
|
13230
13276
|
* ],
|
|
13231
13277
|
* filter: { active: true },
|
|
13232
|
-
* sort: { field: 'count', direction:
|
|
13278
|
+
* sort: { field: 'count', direction: -1 },
|
|
13233
13279
|
* limit: 10
|
|
13234
13280
|
* });
|
|
13281
|
+
* // Result: [{ group: { category: 'work', status: 'open' }, count: 4, sum_amount: 120, avg_score: 3.5 }, ...]
|
|
13235
13282
|
*
|
|
13236
13283
|
* @example
|
|
13237
13284
|
* // StringSet membership grouping
|
|
@@ -13239,7 +13286,7 @@ var init_BaseModel = __esm({
|
|
|
13239
13286
|
* groupBy: [{ field: 'tags', contains: 'urgent' }],
|
|
13240
13287
|
* operations: [{ type: 'count' }]
|
|
13241
13288
|
* });
|
|
13242
|
-
* // Result: { true: 5, false: 23 }
|
|
13289
|
+
* // Result: [{ group: { has_tags_urgent: 'true' }, count: 5 }, { group: { has_tags_urgent: 'false' }, count: 23 }]
|
|
13243
13290
|
*/
|
|
13244
13291
|
static async aggregate(options) {
|
|
13245
13292
|
const ormContext = resolveOrmContext(this);
|
|
@@ -13289,6 +13336,10 @@ var init_BaseModel = __esm({
|
|
|
13289
13336
|
}
|
|
13290
13337
|
}
|
|
13291
13338
|
}
|
|
13339
|
+
const ambiguousKey = findAmbiguousAggregationGroupKey(options.groupBy);
|
|
13340
|
+
if (ambiguousKey !== null) {
|
|
13341
|
+
throw new Error(ambiguousAggregationGroupKeysMessage(ambiguousKey));
|
|
13342
|
+
}
|
|
13292
13343
|
const aggregateScope = this.documentsInScope(options.documents);
|
|
13293
13344
|
const connected = new Set(
|
|
13294
13345
|
this.documentsInScope()
|
|
@@ -13322,7 +13373,8 @@ var init_BaseModel = __esm({
|
|
|
13322
13373
|
const processedResult = this.processAggregationResults(
|
|
13323
13374
|
results,
|
|
13324
13375
|
options,
|
|
13325
|
-
aggregationQuery.aliasMetadata
|
|
13376
|
+
aggregationQuery.aliasMetadata,
|
|
13377
|
+
aggregationQuery.regularGroupBy
|
|
13326
13378
|
);
|
|
13327
13379
|
if (aggregationQuery.aliasMetadata && aggregationQuery.aliasMetadata.length > 0) {
|
|
13328
13380
|
const debugMap = {};
|
|
@@ -13485,9 +13537,13 @@ var init_BaseModel = __esm({
|
|
|
13485
13537
|
const selectParts = [];
|
|
13486
13538
|
const aliasMetadata = [];
|
|
13487
13539
|
const usedAliases = /* @__PURE__ */ new Set();
|
|
13488
|
-
|
|
13489
|
-
selectParts.push(
|
|
13490
|
-
|
|
13540
|
+
regularGroupBy.forEach((field, index) => {
|
|
13541
|
+
selectParts.push(
|
|
13542
|
+
`${quotedMainTable}.${quoteIdentifier(field)} AS ${quoteIdentifier(
|
|
13543
|
+
positionalGroupAlias(index)
|
|
13544
|
+
)}`
|
|
13545
|
+
);
|
|
13546
|
+
});
|
|
13491
13547
|
for (const membership of stringSetMemberships) {
|
|
13492
13548
|
const membershipKey = this.buildMembershipKey(
|
|
13493
13549
|
membership.field,
|
|
@@ -13597,7 +13653,9 @@ var init_BaseModel = __esm({
|
|
|
13597
13653
|
}
|
|
13598
13654
|
aliasMetadata.forEach((detail) => {
|
|
13599
13655
|
const quotedAlias = quoteIdentifier(detail.alias);
|
|
13600
|
-
groupByParts.push(
|
|
13656
|
+
groupByParts.push(
|
|
13657
|
+
`CASE WHEN ${quotedAlias}.${quotedRecordIdColumn} IS NOT NULL THEN 'true' ELSE 'false' END`
|
|
13658
|
+
);
|
|
13601
13659
|
});
|
|
13602
13660
|
if (groupByParts.length > 0) {
|
|
13603
13661
|
sql += ` GROUP BY ${groupByParts.join(", ")}`;
|
|
@@ -13613,7 +13671,9 @@ var init_BaseModel = __esm({
|
|
|
13613
13671
|
);
|
|
13614
13672
|
if (membershipDetail) {
|
|
13615
13673
|
sortExpression = quoteIdentifier(membershipDetail.alias);
|
|
13616
|
-
} else if (
|
|
13674
|
+
} else if (options.operations.some(
|
|
13675
|
+
(operation) => aggregationOperationKey(operation) === requestedField
|
|
13676
|
+
)) {
|
|
13617
13677
|
sortExpression = quoteIdentifier(requestedField);
|
|
13618
13678
|
} else {
|
|
13619
13679
|
sortExpression = `${quotedMainTable}.${quoteIdentifier(
|
|
@@ -13627,99 +13687,50 @@ var init_BaseModel = __esm({
|
|
|
13627
13687
|
if (options.limit) {
|
|
13628
13688
|
sql += ` LIMIT ${options.limit}`;
|
|
13629
13689
|
}
|
|
13630
|
-
return { sql, params, aliasMetadata };
|
|
13631
|
-
}
|
|
13632
|
-
/**
|
|
13633
|
-
* Read one row's operation values, collapsing a single-operation aggregate
|
|
13634
|
-
* to the bare value of that operation.
|
|
13635
|
-
*
|
|
13636
|
-
* This is the platform's one aggregate leaf rule, shared with the DO engine's
|
|
13637
|
-
* `_extractOpValue` (`engines/cloudflare/createDocumentDO.ts`, behind
|
|
13638
|
-
* database aggregates, registered `aggregate` operations, workflow pipeline
|
|
13639
|
-
* steps and `connectDoDb` bindings) and its mirror `aggregateRowsToResult`
|
|
13640
|
-
* in the platform's `src/document-projection.ts` (the document HTTP
|
|
13641
|
-
* aggregate). These model statics used to collapse a lone `count` only and
|
|
13642
|
-
* keep `<type>_<field>` for a lone `sum`/`avg`/`min`/`max`, so the same
|
|
13643
|
-
* declared aggregation answered differently depending on the path taken;
|
|
13644
|
-
* they were the outlier and moved to the shared rule in #3076 (js-bao 0.10.0).
|
|
13645
|
-
*
|
|
13646
|
-
* Both result-shaping branches below call this, so the StringSet-facet path
|
|
13647
|
-
* and the general path cannot drift apart again. Pinned by
|
|
13648
|
-
* `cli/tests/unit/aggregate-shaper-divergence-3076.test.ts` and, end to end
|
|
13649
|
-
* against the HTTP aggregate, by
|
|
13650
|
-
* `tests/api/http/aggregate-shape-convergence-3076.test.ts`.
|
|
13651
|
-
*/
|
|
13652
|
-
static extractAggregationLeaf(row, operations) {
|
|
13653
|
-
const valueFor = (operation) => operation.type === "count" ? row.count : row[`${operation.type}_${operation.field}`];
|
|
13654
|
-
if (operations.length === 1) return valueFor(operations[0]);
|
|
13655
|
-
const leaf = {};
|
|
13656
|
-
for (const operation of operations) {
|
|
13657
|
-
if (operation.type === "count") leaf.count = row.count;
|
|
13658
|
-
else leaf[`${operation.type}_${operation.field}`] = valueFor(operation);
|
|
13659
|
-
}
|
|
13660
|
-
return leaf;
|
|
13690
|
+
return { sql, params, aliasMetadata, regularGroupBy };
|
|
13661
13691
|
}
|
|
13662
13692
|
/**
|
|
13663
|
-
*
|
|
13693
|
+
* Shape SQL rows into aggregation rows: `{ group, ...operationValues }`, in
|
|
13694
|
+
* the order SQL returned them.
|
|
13664
13695
|
*
|
|
13665
|
-
*
|
|
13666
|
-
*
|
|
13696
|
+
* A facet plan's rows carry the member value as `group_key`; a regular
|
|
13697
|
+
* plan's carry each plain level under its positional alias (`_g<i>`, `i`
|
|
13698
|
+
* its index in `regularGroupBy`, the plan's selected plain fields) and each
|
|
13699
|
+
* membership under the alias `aliasMetadata` records for it. A level the
|
|
13700
|
+
* plan did not select (a StringSet facet beside other levels) is `null`.
|
|
13701
|
+
* Without `regularGroupBy`, every string level counts as a plain one. Shared with the DO engine's
|
|
13702
|
+
* `_processAggregationResults` (`engines/cloudflare/createDocumentDO.ts`) and
|
|
13703
|
+
* the platform's document HTTP aggregate (`aggregateRowsToResult`); the three
|
|
13704
|
+
* are pinned against each other by
|
|
13705
|
+
* `cli/tests/unit/aggregate-shaper-divergence-3076.test.ts`.
|
|
13667
13706
|
*/
|
|
13668
|
-
static processAggregationResults(results, options, aliasMetadata) {
|
|
13669
|
-
if (results.length === 0) {
|
|
13670
|
-
return {};
|
|
13671
|
-
}
|
|
13707
|
+
static processAggregationResults(results, options, aliasMetadata, regularGroupBy) {
|
|
13672
13708
|
const membershipAliasLookup = /* @__PURE__ */ new Map();
|
|
13673
13709
|
if (aliasMetadata) {
|
|
13674
13710
|
for (const detail of aliasMetadata) {
|
|
13675
13711
|
membershipAliasLookup.set(detail.membershipKey, detail.alias);
|
|
13676
|
-
membershipAliasLookup.set(detail.originalAlias, detail.alias);
|
|
13677
13712
|
}
|
|
13678
13713
|
}
|
|
13679
|
-
|
|
13680
|
-
|
|
13681
|
-
|
|
13682
|
-
|
|
13683
|
-
|
|
13684
|
-
|
|
13714
|
+
const facet = options.groupBy.length === 1 && typeof options.groupBy[0] === "string" && results.length > 0 && Object.prototype.hasOwnProperty.call(results[0], "group_key");
|
|
13715
|
+
return results.map((row) => {
|
|
13716
|
+
let plainIndex = 0;
|
|
13717
|
+
return aggregationRow(row, options.groupBy, options.operations, (level) => {
|
|
13718
|
+
if (facet) return row.group_key ?? null;
|
|
13719
|
+
if (typeof level === "string") {
|
|
13720
|
+
const index = regularGroupBy ? regularGroupBy.indexOf(level) : plainIndex++;
|
|
13721
|
+
return index < 0 ? null : row[positionalGroupAlias(index)] ?? null;
|
|
13722
|
+
}
|
|
13723
|
+
const resolvedAlias = membershipAliasLookup.get(
|
|
13724
|
+
this.buildMembershipKey(level.field, level.contains)
|
|
13685
13725
|
);
|
|
13686
|
-
|
|
13687
|
-
|
|
13688
|
-
|
|
13689
|
-
const nestedResult = {};
|
|
13690
|
-
for (const row of results) {
|
|
13691
|
-
let current = nestedResult;
|
|
13692
|
-
for (let i = 0; i < options.groupBy.length; i++) {
|
|
13693
|
-
const groupBy = options.groupBy[i];
|
|
13694
|
-
let key;
|
|
13695
|
-
if (typeof groupBy === "string") {
|
|
13696
|
-
key = String(row[groupBy]);
|
|
13697
|
-
} else {
|
|
13698
|
-
const membershipKey = this.buildMembershipKey(
|
|
13699
|
-
groupBy.field,
|
|
13700
|
-
groupBy.contains
|
|
13701
|
-
);
|
|
13702
|
-
const resolvedAlias = membershipAliasLookup.get(membershipKey) || membershipAliasLookup.get(
|
|
13703
|
-
`has_${groupBy.field}_${groupBy.contains}`
|
|
13726
|
+
if (!resolvedAlias) {
|
|
13727
|
+
throw new Error(
|
|
13728
|
+
`Missing alias metadata for StringSet membership ${level.field}:${level.contains}`
|
|
13704
13729
|
);
|
|
13705
|
-
if (!resolvedAlias) {
|
|
13706
|
-
throw new Error(
|
|
13707
|
-
`Missing alias metadata for StringSet membership ${groupBy.field}:${groupBy.contains}`
|
|
13708
|
-
);
|
|
13709
|
-
}
|
|
13710
|
-
key = row[resolvedAlias];
|
|
13711
13730
|
}
|
|
13712
|
-
|
|
13713
|
-
|
|
13714
|
-
|
|
13715
|
-
if (!current[key]) {
|
|
13716
|
-
current[key] = {};
|
|
13717
|
-
}
|
|
13718
|
-
current = current[key];
|
|
13719
|
-
}
|
|
13720
|
-
}
|
|
13721
|
-
}
|
|
13722
|
-
return nestedResult;
|
|
13731
|
+
return row[resolvedAlias] ?? null;
|
|
13732
|
+
});
|
|
13733
|
+
});
|
|
13723
13734
|
}
|
|
13724
13735
|
/**
|
|
13725
13736
|
* Document-style query API - returns single result or null
|
|
@@ -14568,7 +14579,6 @@ __export(browser_exports, {
|
|
|
14568
14579
|
compareRecordIds: () => compareRecordIds,
|
|
14569
14580
|
completeOverlayEntries: () => completeOverlayEntries,
|
|
14570
14581
|
configuredOfflineWindowDays: () => configuredOfflineWindowDays,
|
|
14571
|
-
createModelClass: () => createModelClass,
|
|
14572
14582
|
decideDeferredReplay: () => decideDeferredReplay,
|
|
14573
14583
|
decodeFoldedState: () => decodeFoldedState,
|
|
14574
14584
|
decodeFormat2StoreError: () => decodeFormat2StoreError,
|
|
@@ -20422,7 +20432,7 @@ var DocumentManagerImpl = class {
|
|
|
20422
20432
|
const boundTo = modelClass?._ormContext;
|
|
20423
20433
|
if (boundTo && boundTo !== this.ormContext && !boundTo.destroyed) {
|
|
20424
20434
|
throw new Error(
|
|
20425
|
-
`[initJsBao] Model '${modelName}' is bound to live js-bao instance #${boundTo.instanceId}. Give instance #${this.ormContext.instanceId} its own model class (a fresh
|
|
20435
|
+
`[initJsBao] Model '${modelName}' is bound to live js-bao instance #${boundTo.instanceId}. Give instance #${this.ormContext.instanceId} its own model class (a fresh class from defineModelSchema + attachAndRegisterModel).`
|
|
20426
20436
|
);
|
|
20427
20437
|
}
|
|
20428
20438
|
const { added } = this.modelRegistry.adoptModel(modelClass, {
|
|
@@ -20575,7 +20585,7 @@ function bindModelClass(ctx, modelName, modelClass, onLiveConflict) {
|
|
|
20575
20585
|
}
|
|
20576
20586
|
if (onLiveConflict === "throw") {
|
|
20577
20587
|
throw new Error(
|
|
20578
|
-
`[initJsBao] Model '${modelName}' is bound to live js-bao instance #${current.instanceId}. Give instance #${ctx.instanceId} its own model class (a fresh
|
|
20588
|
+
`[initJsBao] Model '${modelName}' is bound to live js-bao instance #${current.instanceId}. Give instance #${ctx.instanceId} its own model class (a fresh class from defineModelSchema + attachAndRegisterModel).`
|
|
20579
20589
|
);
|
|
20580
20590
|
}
|
|
20581
20591
|
console.warn(
|
|
@@ -20812,36 +20822,6 @@ function buildDefinedModelSchema(input) {
|
|
|
20812
20822
|
};
|
|
20813
20823
|
return schema;
|
|
20814
20824
|
}
|
|
20815
|
-
function createModelClass(config) {
|
|
20816
|
-
const { schema, baseClass = BaseModel2, methods, statics } = config;
|
|
20817
|
-
const ParentClass = baseClass;
|
|
20818
|
-
class GeneratedModel extends ParentClass {
|
|
20819
|
-
}
|
|
20820
|
-
Object.defineProperty(GeneratedModel, "name", {
|
|
20821
|
-
value: schema.options.name,
|
|
20822
|
-
configurable: true
|
|
20823
|
-
});
|
|
20824
|
-
const methodDefinitions = methods?.();
|
|
20825
|
-
if (methodDefinitions) {
|
|
20826
|
-
const descriptors = Object.getOwnPropertyDescriptors(methodDefinitions);
|
|
20827
|
-
for (const [key, descriptor] of Object.entries(descriptors)) {
|
|
20828
|
-
if (key === "constructor") continue;
|
|
20829
|
-
Object.defineProperty(GeneratedModel.prototype, key, descriptor);
|
|
20830
|
-
}
|
|
20831
|
-
}
|
|
20832
|
-
if (statics) {
|
|
20833
|
-
const staticDefinitions = statics(
|
|
20834
|
-
GeneratedModel
|
|
20835
|
-
);
|
|
20836
|
-
const descriptors = Object.getOwnPropertyDescriptors(staticDefinitions);
|
|
20837
|
-
for (const [key, descriptor] of Object.entries(descriptors)) {
|
|
20838
|
-
if (key === "prototype") continue;
|
|
20839
|
-
Object.defineProperty(GeneratedModel, key, descriptor);
|
|
20840
|
-
}
|
|
20841
|
-
}
|
|
20842
|
-
attachAndRegisterModel(GeneratedModel, schema);
|
|
20843
|
-
return GeneratedModel;
|
|
20844
|
-
}
|
|
20845
20825
|
function attachSchemaToClass(modelClass, schema) {
|
|
20846
20826
|
modelClass.schema = schema;
|
|
20847
20827
|
modelClass.modelName = schema.options.name;
|