@lossless.org/nosqldb 10.5.0 → 10.6.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lossless.org/nosqldb",
3
- "version": "10.5.0",
3
+ "version": "10.6.0",
4
4
  "private": false,
5
5
  "description": "A MongoDB-compatible embedded database server with wire protocol support, backed by a high-performance Rust engine.",
6
6
  "exports": {
@@ -14,12 +14,12 @@
14
14
  "devDependencies": {
15
15
  "@api.global/typedserver": "^8.4.6",
16
16
  "@design.estate/dees-element": "^2.2.4",
17
- "@git.zone/tsbuild": "^4.5.0",
18
- "@git.zone/tsbundle": "^2.13.0",
19
- "@git.zone/tsrust": "^1.13.2",
20
- "@git.zone/tstest": "^6.1.1",
17
+ "@git.zone/tsbuild": "^5.0.0",
18
+ "@git.zone/tsbundle": "^2.15.0",
19
+ "@git.zone/tsrust": "^1.15.1",
20
+ "@git.zone/tstest": "^6.2.1",
21
21
  "@lossless.org/client": "0.1.1",
22
- "@types/node": "26.5.1",
22
+ "@types/node": "26.6.1",
23
23
  "mongodb": "^7.5.0"
24
24
  },
25
25
  "dependencies": {
package/readme.md CHANGED
@@ -559,6 +559,17 @@ see the transaction snapshot plus its staged changes. Commit validates conflicts
559
559
  and publishes atomically; abort discards staged changes. Write conflicts report
560
560
  `WriteConflict` (112) with `TransientTransactionError`.
561
561
 
562
+ A staged write conflicts when it touches a document `_id` or a unique index key
563
+ that another transaction or statement wrote after this transaction's snapshot:
564
+ for example two transactions that both read a claim as absent and then insert
565
+ it under the same `_id`, or under distinct `_id`s with the same unique key.
566
+ Statements validate against the snapshot, so such a collision is reported when
567
+ the loser commits; nothing of the losing transaction is published. The driver's
568
+ `withTransaction` retries the transient error, and the retry observes the
569
+ winner. A key already visible in the transaction's snapshot is not a conflict:
570
+ inserting it fails the statement with `DuplicateKey` (11000), as outside a
571
+ transaction.
572
+
562
573
  Commit/abort outcomes and successful retryable statement results survive file
563
574
  storage restart. Duplicate commits replay their outcome; abort after commit
564
575
  reports `TransactionCommitted` (256), commit after abort reports
@@ -1298,7 +1309,7 @@ await collection.findOneAndReplace({ _id: id }, { name: 'Replaced' }, { returnDo
1298
1309
 
1299
1310
  // Element
1300
1311
  { email: { $exists: true } }
1301
- { type: { $type: 'string' } }
1312
+ { type: { $type: 'string' } } { value: { $type: ['int', 'long'] } }
1302
1313
 
1303
1314
  // Array
1304
1315
  { tags: { $all: ['mongodb', 'database'] } }
@@ -1307,8 +1318,28 @@ await collection.findOneAndReplace({ _id: id }, { name: 'Replaced' }, { returnDo
1307
1318
 
1308
1319
  // Regex
1309
1320
  { name: { $regex: /^Al/i } }
1321
+
1322
+ // Aggregation expressions
1323
+ { owner: 'o', $expr: { $lte: [{ $add: ['$retainedBytes', '$reservedBytes', size] }, max] } }
1324
+ { $expr: { $gt: ['$leaseExpiresAt', '$$NOW'] } }
1310
1325
  ```
1311
1326
 
1327
+ `$type` takes a type alias (`'string'`, `'number'`, …), a numeric BSON type
1328
+ code or an array of them, and matches an array field through its elements as
1329
+ well as `'array'` itself.
1330
+
1331
+ `$expr` evaluates an aggregation expression per document in `find`, `count`,
1332
+ `distinct`, `update`, `delete`, `findAndModify` and a `$match` stage, and
1333
+ matches when the result is truthy. It accepts the operators listed under
1334
+ [Aggregation Pipeline](#aggregation-pipeline). Evaluation is charged against
1335
+ the same work budget as every other predicate, and an operator the engine does
1336
+ not implement is refused by name with `TypeMismatch` (code 14) before any
1337
+ document is read. `$$NOW` is one instant per command, as in MongoDB: the
1338
+ engine reads the clock once when the command starts, and the command's filter,
1339
+ every document of a pipeline update, every aggregation stage, `$lookup` and
1340
+ `$unionWith` sub-pipeline, and every `getMore` of its cursor see that instant.
1341
+ Filters outside a command, such as a partial index filter, have no `$$NOW`.
1342
+
1312
1343
  ### Update Operators
1313
1344
 
1314
1345
  ```typescript
@@ -1345,15 +1376,57 @@ const results = await collection.aggregate([
1345
1376
 
1346
1377
  **Supported stages:** `$match`, `$project`, `$group`, `$sort`, `$limit`, `$skip`, `$unwind`, `$lookup`, `$addFields`, `$count`, `$facet`, `$replaceRoot`, `$set`, `$unionWith`, `$out`, `$merge`
1347
1378
 
1348
- **Supported expression operators:** `$eq`, `$and`, `$literal`, `$map`,
1349
- `$objectToArray`, `$setEquals`, plus `$field` paths, `$$variable` references
1350
- including `$$ROOT`, and documents or arrays of those. Every other operator is
1351
- refused by name with `TypeMismatch` (code 14) before any document is read; the
1352
- engine never treats an operator it cannot execute as a literal value.
1379
+ **Supported expression operators:**
1380
+
1381
+ | Group | Operators |
1382
+ | --- | --- |
1383
+ | Comparison | `$eq`, `$ne`, `$gt`, `$gte`, `$lt`, `$lte`, `$cmp` |
1384
+ | Arithmetic | `$add`, `$subtract`, `$multiply`, `$divide` |
1385
+ | Logical | `$and`, `$or`, `$not` |
1386
+ | Conditional and variables | `$cond` (array or `if`/`then`/`else` form), `$ifNull` (two or more operands), `$let` |
1387
+ | Type and arrays | `$isNumber`, `$type`, `$in`, `$size`, `$arrayElemAt` |
1388
+ | Dates | `$dateAdd` |
1389
+ | Collections | `$map`, `$objectToArray`, `$setEquals` |
1390
+ | Literals | `$literal` |
1391
+
1392
+ plus `$field` paths, `$$variable` references including `$$ROOT` and `$$NOW`,
1393
+ and documents or arrays of those. Every other operator is refused by name with
1394
+ `TypeMismatch` (code 14) before any document is read; the engine never treats
1395
+ an operator it cannot execute as a literal value.
1396
+
1397
+ Expressions follow MongoDB's value semantics. A field that is absent is
1398
+ *missing*, which is not `null`: `$type` answers `'missing'`, `$ifNull`
1399
+ replaces both, and comparisons order values as MongoDB does (MinKey, missing,
1400
+ `null`, numbers, strings, objects, arrays, binary data, ObjectIds, booleans,
1401
+ dates, timestamps, regular expressions, MaxKey), so `{ $eq: ['$absent', null] }`
1402
+ is false. Numbers compare by value across their wire types. A path through an
1403
+ array maps over its elements and always answers an array: `'$mailbox.path'`
1404
+ over one looked-up document is `['INBOX']`, so `$arrayElemAt` and `$size`
1405
+ apply to it. Arithmetic answers `null` when an operand is `null` or missing;
1406
+ an int result that overflows widens to a long and a long to a double, a double
1407
+ operand makes the result a double, and `$divide` always answers a double.
1408
+ `$add` accepts one date and answers a date; date minus date is milliseconds.
1409
+ Runtime errors keep MongoDB 7's codes: a non-numeric operand is
1410
+ `TypeMismatch` (14), `can't $divide by zero` is `BadValue` (2), a date that
1411
+ leaves the 64-bit range is `Overflow` (15), and the array and date operators
1412
+ answer MongoDB's location codes (a second date in `$add` 16612, `$in` without
1413
+ an array 40081, `$size` without an array 17124, `$arrayElemAt` 28689–28691,
1414
+ `$dateAdd` 5166403, 5166405, 5166406 and 5439013, a non-string time zone
1415
+ 40517, an unknown unit `FailedToParse` (9)). Decimal128 operands in arithmetic
1416
+ are refused with `TypeMismatch` (14) — decimal arithmetic is not
1417
+ implemented. `$and`, `$or`, `$cond` and `$ifNull`
1418
+ evaluate, and charge, only the operands MongoDB evaluates, so a guard keeps an
1419
+ invalid branch from failing the statement. `$dateAdd` computes in UTC with the
1420
+ units `millisecond` to `year`, clamping `month`, `quarter` and `year` to the
1421
+ end of the target month; a `timezone` other than UTC is refused by name.
1422
+ Within a document expression a member that is missing is still written as
1423
+ `null`, where MongoDB omits the member.
1353
1424
 
1354
1425
  `$lookup` supports the equality form and a bounded correlated pipeline form with
1355
- `let`, an optional `$match` using `$expr`/`$and`/`$eq`, and an optional following
1356
- `$limit`. Correlated string equality predicates can use a full-key or subset
1426
+ `let`, an optional `$match` (any filter, `$expr` included), and an optional
1427
+ following `$limit`. A `let` variable that reads an absent local field is bound
1428
+ to the missing value, as in MongoDB. Correlated string equality predicates
1429
+ (`$expr` with `$eq`, optionally under `$and`) can use a full-key or subset
1357
1430
  foreign equality index to reduce candidates when available. Candidates are
1358
1431
  paged, the complete expression remains authoritative, and `$limit` counts only
1359
1432
  exact expression matches. Other BSON value shapes use the bounded scan path.
@@ -1368,6 +1441,18 @@ matcher cannot pre-compile fall back to the unnarrowed load, so results are
1368
1441
  unchanged. The materialization limit therefore applies to the matched set, which
1369
1442
  is what lets `countDocuments(filter)` run against collections larger than it.
1370
1443
 
1444
+ The scalar members of a `$in` or `$nin` list are looked up in a set built when
1445
+ the filter is validated, so a document costs the size of its value, not the
1446
+ length of the list: `countDocuments({ _id: { $in: ids } })` answers for tens of
1447
+ thousands of ids. Equality is unchanged: numbers of every type are equal by
1448
+ value (1, `NumberLong(1)`, 1.0 and `NumberDecimal("1.00")`), an array field
1449
+ matches through its elements or as a whole, and a missing field matches a list
1450
+ that holds `null`. Documents, arrays and regular expressions in a list are
1451
+ compared one by one and charged as such. A regular expression in a list is
1452
+ still compared as a value and not yet applied to strings, which differs from
1453
+ MongoDB, where it matches the strings it describes; that difference is a known
1454
+ open defect.
1455
+
1371
1456
  **Group accumulators:** `$sum`, `$avg`, `$min`, `$max`, `$first`, `$last`, `$push`, `$addToSet`, `$count`
1372
1457
 
1373
1458
  `$group._id` accepts a literal (`null`, a number, a string), a `$field`
@@ -1397,12 +1482,50 @@ await collection.dropIndexes(); // drop all except _id
1397
1482
  > 🛡️ **Unique indexes are enforced at the engine level.** Duplicate values are rejected with a `DuplicateKey` error (code 11000) *before* the document is written to disk — on `insertOne`, `updateOne`, `findAndModify`, and upserts. Index definitions and entries are committed with native storage and survive restart.
1398
1483
 
1399
1484
  NoSQLDB supports ascending and descending index keys with `name`, `unique`,
1400
- `sparse`, and `expireAfterSeconds` options. It accepts `background` as a
1401
- validated no-op and index version `v: 2`. Unsupported options, including
1402
- `partialFilterExpression`, `collation`, and `hidden`, are rejected before any
1403
- index in the request is created. Native maintenance expires eligible BSON dates
1404
- and date arrays in bounded batches, preserving publication holds and active
1405
- snapshots.
1485
+ `sparse`, `partialFilterExpression` and `expireAfterSeconds` options. It
1486
+ accepts `background` as a validated no-op and index version `v: 2`.
1487
+ Unsupported options, including `collation` and `hidden`, are rejected before
1488
+ any index in the request is created. Native maintenance expires eligible BSON
1489
+ dates and date arrays in bounded batches, preserving publication holds and
1490
+ active snapshots.
1491
+
1492
+ **Partial indexes.** An index with a `partialFilterExpression` holds only the
1493
+ documents that match the filter, and a unique partial index enforces
1494
+ uniqueness among those documents only:
1495
+
1496
+ ```typescript
1497
+ await jobs.createIndex(
1498
+ { activeClaimKey: 1 },
1499
+ { unique: true, partialFilterExpression: { activeClaimKey: { $exists: true } } },
1500
+ );
1501
+ await pulls.createIndex(
1502
+ { org: 1, repo: 1, sourceRef: 1, targetRef: 1 },
1503
+ { unique: true, partialFilterExpression: { state: 'open' } },
1504
+ );
1505
+ ```
1506
+
1507
+ The filter accepts MongoDB's subset: equality (`field: value` or `$eq`),
1508
+ `$exists: true`, `$gt`, `$gte`, `$lt`, `$lte`, `$type`, `$in`, `$and` and
1509
+ `$or`, nested at most four expression levels deep. Any other expression —
1510
+ `$exists: false`, `$ne`, `$nin`, `$not`, `$nor`, regular expressions,
1511
+ `$elemMatch`, `$size`, `$expr`, and a regular expression inside `$in` — and a
1512
+ filter combined with `sparse` are refused with `CannotCreateIndex` (code 67);
1513
+ a malformed filter is `BadValue` (2) and a non-document is `TypeMismatch` (14).
1514
+ An update that moves a document into the filter claims its key, one that moves
1515
+ it out releases it, and transactions claim keys the same way, so a racing
1516
+ claim is a `WriteConflict` (112) with `TransientTransactionError`.
1517
+ `listIndexes`, canonical export and import report the filter exactly as it was
1518
+ created. The filter identifies the index: the same name with a different
1519
+ filter is `IndexKeySpecsConflict` (86); the same key with an equivalent filter
1520
+ under another name is `IndexOptionsConflict` (85); a different filter over the
1521
+ same key under another name is a separate index. Equivalence treats implicit
1522
+ equality and `$eq`, the order of conjuncts and `$or` branches, numeric wire
1523
+ types and `$in` duplicates as MongoDB's normalised expressions do. The query
1524
+ planner never selects a partial index on its own; a `hint` naming one reads
1525
+ only its members, as MongoDB's hinted scan does. A partial TTL index expires
1526
+ only its members. Engines older than this release refuse a storage root that
1527
+ holds a partial index instead of enforcing its uniqueness over every
1528
+ document.
1406
1529
 
1407
1530
  `find()` and collection `aggregate()` accept `hint` as an index name (including
1408
1531
  `_id_`) or an exact ascending/descending key pattern. Key-pattern order matters;
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@lossless.org/nosqldb',
6
- version: '10.5.0',
6
+ version: '10.6.0',
7
7
  description: 'A MongoDB-compatible embedded database server with wire protocol support, backed by a high-performance Rust engine.'
8
8
  }