@truenas/api-client 4.0.1 → 4.0.2

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 CHANGED
@@ -183,6 +183,65 @@ discovery route that resolves against whatever the appliance turned out to be.
183
183
  On the named route it cannot: the client class is picked from the version you
184
184
  stated, so `ops` is that version's mappings whether or not the server agrees.
185
185
 
186
+ `ops` is deliberately flat — `OperationMappings`, not parameterized by the
187
+ directory — so every operation is callable whether or not you pinned a version.
188
+ That is what lets an operation paper over a difference the type system would
189
+ otherwise force the caller to handle.
190
+
191
+ ```ts
192
+ // The same call on every supported version.
193
+ const sessions = await firstValueFrom(
194
+ client.ops.smbStatus({ infoLevel: 'SESSIONS' })
195
+ );
196
+
197
+ // A client count is this composed, not a second operation.
198
+ const count = await firstValueFrom(
199
+ client.ops.smbStatus({
200
+ infoLevel: 'SESSIONS',
201
+ options: { count: true },
202
+ statusOptions: { fast: true },
203
+ })
204
+ );
205
+ ```
206
+
207
+ `smbStatus` is worth singling out, because it is the first operation whose
208
+ v25.10 leg is invisible to `middlewared --dump-api`. `smb.status` is public on
209
+ v26+ and generated like any other method; on v25.10 it is the same method taking
210
+ the same four positional arguments and returning the same `list | dict | int`,
211
+ but middleware declares it `private=True`, so the dump omits it and no generated
212
+ v25.10 type mentions it. The v25.10 client therefore *asserts* the method exists
213
+ rather than reading it from the directory — a single narrow declaration admitting
214
+ one method name, one argument tuple and one return type, checked against
215
+ middleware source instead of against the dump.
216
+
217
+ Three consequences worth knowing before you reach for it.
218
+
219
+ **It needs a full-admin session on v25.10.** Being private there decides
220
+ authorization, not just documentation. `smb.status` declares no roles on
221
+ v25.10, and middleware role-registers a method only `if roles:` — so it is in no
222
+ role's allowlist, and only a **non-STIG full-admin** session, whose allowlist is
223
+ the wildcard `{ method: '*', resource: '*' }`, reaches it. A session holding
224
+ exactly `SHARING_SMB_READ` is refused with `EACCES` on v25.10 and succeeds on
225
+ v26+, where the method carries that role. Under STIG, full admin is expanded to
226
+ the union of its roles' allowlists rather than the wildcard, so it is refused on
227
+ v25.10 as well. The client does not pre-empt any of this: you get middleware's
228
+ own error, on the version where it applies.
229
+
230
+ **It logs server-side on every dispatch**, which matters if you poll it. v25.10
231
+ is in maintenance and that cost is accepted there, but it is not a pattern to
232
+ extend.
233
+
234
+ **The result is the union middleware sends** — which arm arrives is decided by
235
+ the options, not the info level — so callers narrow it themselves.
236
+
237
+ These types are deliberately not the generated ones. `SmbStatusOptions` is not
238
+ v26's `SMBStatusOptions`, and `SmbStatusResponse` is not that method's generated
239
+ response: the generated types describe one version's dump, while these describe
240
+ the contract both versions honour. They are named `Smb…` rather than `SMB…` so
241
+ the two cannot be confused at a call site — and `SmbStatusOptions` is the one
242
+ pair where the names sit closest, since it is the fourth positional argument on
243
+ both versions.
244
+
186
245
  ## Documentation
187
246
 
188
247
  The API reference is generated from the TSDoc comments in the source with
package/dist/index.cjs CHANGED
@@ -3449,6 +3449,16 @@ function toAppState(state) {
3449
3449
  }
3450
3450
  }
3451
3451
 
3452
+ // src/utils/smb-status.utils.ts
3453
+ function toSmbStatusParams(request = {}) {
3454
+ return [
3455
+ request.infoLevel ?? "ALL",
3456
+ request.filters ?? [],
3457
+ request.options ?? {},
3458
+ request.statusOptions ?? {}
3459
+ ];
3460
+ }
3461
+
3452
3462
  // src/client/truenas-api-client-v25-10.ts
3453
3463
  var TrueNasApiClientV2510 = class extends TrueNasApiClient {
3454
3464
  /**
@@ -3488,6 +3498,42 @@ var TrueNasApiClientV2510 = class extends TrueNasApiClient {
3488
3498
  );
3489
3499
  }
3490
3500
  return this.api.job("virt.instance.delete", [id]);
3501
+ },
3502
+ // The private leg. See `PrivateSmbStatusCall` for why this is a cast and
3503
+ // not a directory lookup.
3504
+ //
3505
+ // v25.10 answers rather than refusing, for a caller with the authority.
3506
+ // Being private does not stop the method running: `ws_handler/rpc.py`
3507
+ // logs a warning for a private call over websocket and dispatches it
3508
+ // anyway — the `# FIXME: Eventually, prohibit this` above that branch is
3509
+ // still unresolved on master.
3510
+ //
3511
+ // It does decide who may run it. `smb.status` carries no `roles=` here,
3512
+ // and middleware role-registers a method only `if roles:`, so it is in no
3513
+ // role's allowlist and only the non-STIG full-admin wildcard reaches it.
3514
+ // That is documented on `OperationMappings.smbStatus`; this leg does not
3515
+ // pre-empt it. A caller without the authority gets middleware's `EACCES`,
3516
+ // which is the honest answer and the server's to give.
3517
+ //
3518
+ // The server-side warning is per dispatch, and a client count is exactly
3519
+ // the sort of thing a caller polls. Reported at debug rather than warn,
3520
+ // because nothing here is degraded or unhonoured — the request is served
3521
+ // in full — and left to the caller to weigh.
3522
+ //
3523
+ // Future tense, and for the same reason as `containerDelete` above: this
3524
+ // runs when the operation is built, and `api.call` is cold, so no request
3525
+ // has gone out yet and may never. One built observable subscribed twice
3526
+ // dispatches twice; this line is what will happen, not a record that it
3527
+ // did.
3528
+ smbStatus: (request) => {
3529
+ this.logger.debug(
3530
+ "smbStatus: smb.status is private on v25.10; each dispatch will log a warning server-side and needs a non-STIG full-admin session",
3531
+ { method: "smb.status", infoLevel: request?.infoLevel ?? "ALL" }
3532
+ );
3533
+ return this.api.call(
3534
+ "smb.status",
3535
+ toSmbStatusParams(request)
3536
+ );
3491
3537
  }
3492
3538
  };
3493
3539
  }
@@ -3575,7 +3621,16 @@ var TrueNasApiClientV26 = class extends TrueNasApiClient {
3575
3621
  containerDelete: (id, options) => this.api.job(
3576
3622
  "container.delete",
3577
3623
  options ? [parseInt(id, 10), options] : [parseInt(id, 10)]
3578
- )
3624
+ ),
3625
+ // `smb.status` is public here and gated on `SHARING_SMB_READ`, so it is
3626
+ // an ordinary `call` read straight out of the generated directory — the
3627
+ // one leg of this operation that needs no assertion about the server.
3628
+ //
3629
+ // Not a query verb: middleware returns the same `list | dict | int`
3630
+ // polymorphism a `.query` does, but the generator did not mark the entry
3631
+ // with an `entity`, so `api.query` does not accept it and the union is
3632
+ // handed to the caller to narrow.
3633
+ smbStatus: (request) => this.api.call("smb.status", toSmbStatusParams(request))
3579
3634
  };
3580
3635
  }
3581
3636
  };
@@ -3656,7 +3711,16 @@ var TrueNasApiClientV27 = class extends TrueNasApiClient {
3656
3711
  containerDelete: (id, options) => this.api.job(
3657
3712
  "container.delete",
3658
3713
  options ? [parseInt(id, 10), options] : [parseInt(id, 10)]
3659
- )
3714
+ ),
3715
+ // `smb.status` is public here and gated on `SHARING_SMB_READ`, so it is
3716
+ // an ordinary `call` read straight out of the generated directory — the
3717
+ // one leg of this operation that needs no assertion about the server.
3718
+ //
3719
+ // Not a query verb: middleware returns the same `list | dict | int`
3720
+ // polymorphism a `.query` does, but the generator did not mark the entry
3721
+ // with an `entity`, so `api.query` does not accept it and the union is
3722
+ // handed to the caller to narrow.
3723
+ smbStatus: (request) => this.api.call("smb.status", toSmbStatusParams(request))
3660
3724
  };
3661
3725
  }
3662
3726
  };