@lossless.org/client 1.14.0 → 1.15.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/client",
3
- "version": "1.14.0",
3
+ "version": "1.15.0",
4
4
  "private": false,
5
5
  "description": "One typed client for NoSQLDB, MongoDB, SQLDB, MariaDB, ClickHouse and S3 object storage.",
6
6
  "exports": {
@@ -82,7 +82,7 @@
82
82
  "@aws-sdk/client-s3": "^3.1039.0",
83
83
  "@clickhouse/client": "1.23.1",
84
84
  "@git.zone/tsbuild": "5.0.0",
85
- "@git.zone/tsrun": "3.0.1",
85
+ "@git.zone/tsrun": "3.0.2",
86
86
  "@git.zone/tstest": "6.3.2",
87
87
  "@lossless.org/nosqldb": "8.0.2",
88
88
  "@lossless.org/nosqldb-qualified": "npm:@lossless.org/nosqldb@10.7.2",
package/readme.md CHANGED
@@ -108,6 +108,8 @@ Family imports preserve the established SmartData and SmartBucket names and cons
108
108
  | Transactions ended by close | a `session.withTransaction()` whose session `db.close()` ends while it runs rejects with `SmartdataPersistenceError` code `unsupported_operation` — "nothing was committed" before the commit, "the commit outcome is unknown" during it — instead of resolving with the callback's result |
109
109
  | Transaction commit outcome | `SmartdataSession.getCommitOutcome(error)` states for every value a `session.withTransaction()` rejected with whether the transaction could have committed: `not_committed` (nothing was committed) or `unknown` (a commit was sent and never confirmed); the rejection itself keeps its identity |
110
110
  | Cancellation | `signal` on every exact call, `atomicDeleteMany()` and `session.withTransaction(callback, { signal })`, which interrupts the session's operation in flight, rolls the attempt back and never commits after an abort |
111
+ | Nested array paths | `@svDb({ arrayPaths: [...] })` declares nested arrays below a persisted root; `$push`, `$addToSet` and `$pull` in `atomicUpdate()`, `atomicUpdateMany()`, `atomicFindOneAndUpdate()` and `atomicUpsertMany()` accept them, while undeclared and non-array nested paths stay refused with `invalid_argument` |
112
+ | Grouped totals over nested paths | `getGroupedTotals()` / `getGroupedTotalsPage()` group by and sum nested paths declared through `atomicPaths` or `arrayPaths`, and `unwind: path` groups by each element of one declared array |
111
113
  | Query-plan inspection | `Model.inspectQueryPlan(db, { filter, sort?, projection?, limit?, timeoutMS?, signal? })` — the winning plan's stages (`stage`, `depth`, `indexName`, `direction`) and `nReturned`/`totalKeysExamined`/`totalDocsExamined`, read-only and bounded, so a consumer proves a model query uses its intended index without raw driver access |
112
114
  | Disposable test databases on a unix socket | `SmartdataTestDatabase.create({ connection: { socketPath } })` from `@lossless.org/client/testsupport` reaches an engine that listens only on a unix-domain socket, fault helpers included; the loopback `host`/`port` form is unchanged |
113
115
 
@@ -130,8 +132,8 @@ The document model runs on both NoSQLDB and MongoDB, but several APIs do not mea
130
132
 
131
133
  | Document API | On the NoSQLDB engine | On MongoDB |
132
134
  | --- | --- | --- |
133
- | `getGroupedTotals()` / `getGroupedTotalsPage()` with one `groupBy` field | exact, including `sums` | exact |
134
- | the same with **two** `groupBy` fields | exact, `sums` included, on an engine that identifies itself as NoSQLDB 10.5.0 or newer: that release evaluates the composite group key per document. Below it the call is refused with `unsupported_operation` naming both fields and the single-field form, because the engine leaves the composite group key unresolved and answers with a single row whose group values are the literal field paths (`'$owner'`) and whose count is the whole filtered collection; the refusal is the guard against that wrong number, not a fix for it | exact |
135
+ | `getGroupedTotals()` / `getGroupedTotalsPage()` with one `groupBy` field | exact, including `sums`, declared nested paths and `unwind` of a declared array, on 8.0.2 and 10.7.2 alike | exact |
136
+ | the same with **two** `groupBy` fields | exact, `sums`, nested paths and `unwind` included, on an engine that identifies itself as NoSQLDB 10.5.0 or newer: that release evaluates the composite group key per document. Below it the call is refused with `unsupported_operation` naming both fields and the single-field form, because the engine leaves the composite group key unresolved and answers with a single row whose group values are the literal field paths (`'$owner'`) and whose count is the whole filtered collection; the refusal is the guard against that wrong number, not a fix for it | exact |
135
137
  | guarded `$expr` atomic filters (`$add`, `$lte`, `$ifNull`) | exact on 10.6.0 and newer: the engine evaluates the client's fail-closed compilation (`$cond`, `$let`, `$isNumber`, `$type`), so the guard admits within the limit, refuses beyond it and never matches a stored value that is not a finite number. 8.0.2 rejects the compilation with `invalid argument: $cond` and leaves the document untouched | exact |
136
138
  | `watch()` change streams | unsupported. `NoSqlConnection` refuses with `unsupported_capability`, and so does a plain `SmartdataDb` whose engine identified itself; against an engine that answers no identity the refusal instead arrives from the engine as `CommandNotFound` once the change stream is read | supported on replica sets |
137
139
  | model-declared partial indexes (`partialFilterExpression`) | not expressible: the index-option type rejects it and the runtime refuses it with `invalid_configuration`. A client decision, not an engine limit on every release — 10.6.0 and newer create partial indexes, 8.0.2 refuses the option with `InvalidOptions` (72) — because one model declaration has to hold on every qualified backend. A migration may create one on 10.7.2; `toleratedIndexNames` accepts it and inspection reports its filter | not expressible, but a migration may own one; inspection reports its filter |
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@lossless.org/client',
6
- version: '1.14.0',
6
+ version: '1.15.0',
7
7
  description: 'One typed client for NoSQLDB, MongoDB, SQLDB, MariaDB, ClickHouse and S3 object storage.'
8
8
  }
@@ -2498,13 +2498,21 @@ export class SmartdataCollection<T> {
2498
2498
  * Executes a bounded grouped-totals aggregation over already-validated
2499
2499
  * stages and never exposes the aggregation cursor. Prefer the validated
2500
2500
  * static `SmartDataDbDoc.getGroupedTotals()`, which owns input validation
2501
- * before this raw persistence boundary.
2501
+ * before this raw persistence boundary. `unwindPath` names an already
2502
+ * validated field path whose array is unwound between the match and the
2503
+ * group stage.
2502
2504
  */
2503
2505
  public async aggregateGroupedTotals(
2504
2506
  matchArg: plugins.mongodb.Filter<plugins.mongodb.Document> | undefined,
2505
2507
  groupArg: plugins.mongodb.Document,
2506
2508
  limitArg: number,
2507
- opts?: { session?: TSmartdataOrdinarySession; maxTimeMS?: number; timeoutMS?: number; signal?: AbortSignal }
2509
+ opts?: {
2510
+ session?: TSmartdataOrdinarySession;
2511
+ maxTimeMS?: number;
2512
+ timeoutMS?: number;
2513
+ signal?: AbortSignal;
2514
+ unwindPath?: string;
2515
+ }
2508
2516
  ): Promise<plugins.mongodb.Document[]> {
2509
2517
  return this.runWithOrdinarySession(
2510
2518
  opts?.session,
@@ -2515,6 +2523,9 @@ export class SmartdataCollection<T> {
2515
2523
  if (matchArg && Object.keys(matchArg).length > 0) {
2516
2524
  pipeline.push({ $match: matchArg });
2517
2525
  }
2526
+ if (opts?.unwindPath !== undefined) {
2527
+ pipeline.push({ $unwind: '$' + opts.unwindPath });
2528
+ }
2518
2529
  pipeline.push({ $group: groupArg });
2519
2530
  pipeline.push({ $limit: limitArg });
2520
2531
  const cursor = this.mongoDbCollection.aggregate(pipeline, {