@truenas/api-client 3.0.1 → 3.0.3
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 +49 -7
- package/dist/index.cjs +92 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +151 -5
- package/dist/index.d.ts +151 -5
- package/dist/index.js +92 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -13015,6 +13015,33 @@ interface ContainerStopOptions$1 {
|
|
|
13015
13015
|
timeout?: number;
|
|
13016
13016
|
force: boolean;
|
|
13017
13017
|
}
|
|
13018
|
+
/**
|
|
13019
|
+
* Options for deleting a container (unified interface)
|
|
13020
|
+
*
|
|
13021
|
+
* Both are optional and both default to off, matching middleware. Neither has a
|
|
13022
|
+
* counterpart on v25.10 — `virt.instance.delete` takes an id and nothing else —
|
|
13023
|
+
* so the v25.10 client cannot honour them; it says so rather than dropping them
|
|
13024
|
+
* quietly, because `recursive` in particular destroys data.
|
|
13025
|
+
*/
|
|
13026
|
+
interface ContainerDeleteOptions$1 {
|
|
13027
|
+
/**
|
|
13028
|
+
* Stop the container first if it is not already stopped. Without it, v26+
|
|
13029
|
+
* refuses to delete a running or suspended container rather than tearing it
|
|
13030
|
+
* down underneath itself.
|
|
13031
|
+
*/
|
|
13032
|
+
force?: boolean;
|
|
13033
|
+
/**
|
|
13034
|
+
* Destroy the container's dataset together with its child datasets and
|
|
13035
|
+
* snapshots, any clones of those snapshots wherever they live in the pool,
|
|
13036
|
+
* and any holds on them.
|
|
13037
|
+
*
|
|
13038
|
+
* Releasing a hold can break a replication task that depends on it, and none
|
|
13039
|
+
* of what this destroys is recoverable. Without it, v26+ refuses to delete a
|
|
13040
|
+
* container whose dataset has children or snapshots — which is the refusal
|
|
13041
|
+
* this option exists to override, deliberately.
|
|
13042
|
+
*/
|
|
13043
|
+
recursive?: boolean;
|
|
13044
|
+
}
|
|
13018
13045
|
/**
|
|
13019
13046
|
* Options for restarting a container (unified interface)
|
|
13020
13047
|
*/
|
|
@@ -13058,8 +13085,14 @@ interface ContainerRestartOptions {
|
|
|
13058
13085
|
*
|
|
13059
13086
|
* To add new operations:
|
|
13060
13087
|
* 1. Add the method signature here
|
|
13061
|
-
* 2. Implement in
|
|
13062
|
-
*
|
|
13088
|
+
* 2. Implement it in every client's `createOperations()` —
|
|
13089
|
+
* `TrueNasApiClientV2510`, `TrueNasApiClientV26`, `TrueNasApiClientV27`
|
|
13090
|
+
*
|
|
13091
|
+
* This list used to name only v25.10 and v26, which is how a new operation
|
|
13092
|
+
* would have quietly missed v27. It is not the real safety net either: adding a
|
|
13093
|
+
* member here fails to compile in every client that has not implemented it, and
|
|
13094
|
+
* that is what actually enumerates them. Keep the list current, but trust the
|
|
13095
|
+
* compiler.
|
|
13063
13096
|
*/
|
|
13064
13097
|
interface OperationMappings {
|
|
13065
13098
|
/**
|
|
@@ -13086,6 +13119,22 @@ interface OperationMappings {
|
|
|
13086
13119
|
* - v26+: Emits Job updates (stop phase), then null (sync start)
|
|
13087
13120
|
*/
|
|
13088
13121
|
containerRestart: (id: string, options: ContainerRestartOptions) => Observable<Job | null>;
|
|
13122
|
+
/**
|
|
13123
|
+
* Delete a container
|
|
13124
|
+
* - v25.10: `virt.instance.delete`, already a job — emits Job updates
|
|
13125
|
+
* - v26+: `container.delete`, made a job in v26.0.0 — emits Job updates
|
|
13126
|
+
*
|
|
13127
|
+
* A job on every supported version, so unlike `containerStart` this one does
|
|
13128
|
+
* not change shape across them. It is exposed here because the alternative is
|
|
13129
|
+
* a caller reaching for `api.call('container.delete', …)`, which is the wrong
|
|
13130
|
+
* verb: the method moved out of the call directory when middleware made it a
|
|
13131
|
+
* job, so that does not compile on v26+ and would not track the job if it did.
|
|
13132
|
+
*
|
|
13133
|
+
* `options` are honoured on v26+ only. v25.10's `virt.instance.delete` takes
|
|
13134
|
+
* an id and nothing else; passing them there is logged rather than silently
|
|
13135
|
+
* ignored, because `recursive` destroys data that cannot be recovered.
|
|
13136
|
+
*/
|
|
13137
|
+
containerDelete: (id: string, options?: ContainerDeleteOptions$1) => Observable<Job | null>;
|
|
13089
13138
|
}
|
|
13090
13139
|
|
|
13091
13140
|
/**
|
|
@@ -27510,6 +27559,19 @@ declare const SUPPORTED_API_VERSIONS: readonly ["v25.10.0", "v25.10.1", "v25.10.
|
|
|
27510
27559
|
* how the missing 22 methods went unnoticed in the first place.
|
|
27511
27560
|
*/
|
|
27512
27561
|
type DefaultApiDirectory = ApiDirectory$7;
|
|
27562
|
+
/**
|
|
27563
|
+
* The surface a named version derives, falling back when nothing was narrowed.
|
|
27564
|
+
*
|
|
27565
|
+
* `V` only pins a directory when inference narrowed it. A wrapper typed
|
|
27566
|
+
* `(version: SupportedApiVersion)` widens it back to the whole union, and
|
|
27567
|
+
* indexing by a union yields a union of directories whose usable methods are
|
|
27568
|
+
* their *intersection* — narrower than the default surface, so naming the
|
|
27569
|
+
* version would buy fewer methods than naming nothing. That case falls back to
|
|
27570
|
+
* {@link DefaultApiDirectory} instead, matching every other shape that loses
|
|
27571
|
+
* the literal. A partial union still derives: methods common to the versions
|
|
27572
|
+
* named is the right answer for "one of these".
|
|
27573
|
+
*/
|
|
27574
|
+
type DerivedDirectory<V extends SupportedApiVersion> = SupportedApiVersion extends V ? DefaultApiDirectory : ApiDirectoryByVersion[V];
|
|
27513
27575
|
/** Options for {@link createTrueNasClient}. */
|
|
27514
27576
|
interface CreateClientOptions {
|
|
27515
27577
|
/** System UUID. */
|
|
@@ -27533,12 +27595,22 @@ interface CreateClientOptions {
|
|
|
27533
27595
|
* through the client, to the connection.
|
|
27534
27596
|
*/
|
|
27535
27597
|
logger?: Logger;
|
|
27598
|
+
/**
|
|
27599
|
+
* The appliance's API version, when the caller already knows it.
|
|
27600
|
+
*
|
|
27601
|
+
* Supplying it skips version discovery entirely — no `GET /api/versions`, no
|
|
27602
|
+
* CORS fallback — and *derives* the client's typed surface from the string,
|
|
27603
|
+
* so `version: 'v27.0.0'` yields `TrueNasApiClient<ApiDirectoryV27_0_0>`
|
|
27604
|
+
* without the caller asserting it through a type parameter.
|
|
27605
|
+
*/
|
|
27606
|
+
version?: SupportedApiVersion;
|
|
27536
27607
|
}
|
|
27537
27608
|
/**
|
|
27538
27609
|
* Creates a version-specific TrueNAS API client.
|
|
27539
27610
|
*
|
|
27540
27611
|
* 1. Discovers the API version (`GET /api/versions`), asking every hostname in
|
|
27541
|
-
* parallel. The first usable answer wins.
|
|
27612
|
+
* parallel. The first usable answer wins — *unless* `opts.version` says
|
|
27613
|
+
* which version this is, in which case discovery is skipped entirely.
|
|
27542
27614
|
* 2. Selects the matching client implementation (`v25.10.x` -> `TrueNasApiClientV2510`,
|
|
27543
27615
|
* `v26.x.y` -> `TrueNasApiClientV26`, `v27.x.y` -> `TrueNasApiClientV27`).
|
|
27544
27616
|
* 3. Instantiates and returns it.
|
|
@@ -27569,9 +27641,77 @@ interface CreateClientOptions {
|
|
|
27569
27641
|
* supported version. Operations that must work across versions belong on
|
|
27570
27642
|
* `client.ops`, which resolves them at runtime.
|
|
27571
27643
|
*
|
|
27644
|
+
* ## Naming the version instead
|
|
27645
|
+
*
|
|
27646
|
+
* A caller that already knows its appliance — a UI served by the appliance
|
|
27647
|
+
* itself, a test harness against a pinned image — can say so and skip discovery
|
|
27648
|
+
* altogether:
|
|
27649
|
+
*
|
|
27650
|
+
* ```typescript
|
|
27651
|
+
* const client = await createTrueNasClient({
|
|
27652
|
+
* uuid, hostnames, enabled: true, version: 'v27.0.0',
|
|
27653
|
+
* });
|
|
27654
|
+
* // client: TrueNasApiClient<ApiDirectoryV27_0_0>, derived from the string
|
|
27655
|
+
* ```
|
|
27656
|
+
*
|
|
27657
|
+
* The surface is *derived* rather than asserted: `ApiDirectoryByVersion` maps
|
|
27658
|
+
* the version string to its directory, so the caller writes no cast and names
|
|
27659
|
+
* no directory type. A version this package ships no types for does not
|
|
27660
|
+
* compile.
|
|
27661
|
+
*
|
|
27662
|
+
* **The derivation needs the version to be literal at the call site.** It comes
|
|
27663
|
+
* from inference on `{ version: V }`, so it holds for a string literal written
|
|
27664
|
+
* in the options object (or a `const`-typed one). It does not survive
|
|
27665
|
+
* indirection:
|
|
27666
|
+
*
|
|
27667
|
+
* - `createTrueNasClient<D>({ …, version: 'v27.0.0' })` — an explicit type
|
|
27668
|
+
* argument makes the derived overload inapplicable, so `D` wins.
|
|
27669
|
+
* - `(v?: SupportedApiVersion) => createTrueNasClient({ …, version: v })` — the
|
|
27670
|
+
* property is `SupportedApiVersion | undefined`, which no `V` satisfies.
|
|
27671
|
+
* - `(v: SupportedApiVersion) => …` — reaches this overload, but `V` widens to
|
|
27672
|
+
* the whole union, which derives nothing.
|
|
27673
|
+
* - `const opts: CreateClientOptions = { …, version: 'v27.0.0' }` — the
|
|
27674
|
+
* annotation widens the property before the call sees it.
|
|
27675
|
+
*
|
|
27676
|
+
* All of them compile, run against the named version, and type as
|
|
27677
|
+
* {@link DefaultApiDirectory}. That fails in the safe direction — understated
|
|
27678
|
+
* types give a compile error at the method call rather than a runtime surprise —
|
|
27679
|
+
* but it is silent, so a wrapper that forwards a version gets none of the
|
|
27680
|
+
* surface it named. Keep the literal at the call site, and if you are adding
|
|
27681
|
+
* `version` to an existing `createTrueNasClient<ApiDirectoryV26_0_0>(opts)`
|
|
27682
|
+
* call, delete the type argument in the same edit.
|
|
27683
|
+
*
|
|
27684
|
+
* Two consequences worth knowing before reaching for it.
|
|
27685
|
+
*
|
|
27686
|
+
* It is a stronger claim than `D` alone, because it also picks the websocket
|
|
27687
|
+
* path. Naming `v27.0.0` at a v26 appliance connects on `/api/v27.0.0` with v27
|
|
27688
|
+
* types over a v26 server, and discovery cannot correct it — declining
|
|
27689
|
+
* discovery is the whole point. `D` on its own only mistyped the surface; this
|
|
27690
|
+
* mistypes the surface *and* dials the wrong number.
|
|
27691
|
+
*
|
|
27692
|
+
* Compatibility is still checked. Skipping discovery skips the network round
|
|
27693
|
+
* trip, not the range check, which is local and free. Two refusals reach a
|
|
27694
|
+
* caller, and they are not interchangeable:
|
|
27695
|
+
*
|
|
27696
|
+
* - a string that is not a `SupportedApiVersion` at all — only reachable from
|
|
27697
|
+
* JavaScript — throws a plain `Error` naming the versions that are.
|
|
27698
|
+
* - a version this package ships types for but cannot build a client for
|
|
27699
|
+
* throws {@link VersionTooNewError}, the same error discovery raises.
|
|
27700
|
+
*
|
|
27701
|
+
* The second is not hypothetical. `MAX_SUPPORTED_VERSION` is a hand-written
|
|
27702
|
+
* literal, and while it matches the newest generated version today, a
|
|
27703
|
+
* regeneration can add a year before anyone writes its client — at which point
|
|
27704
|
+
* that version is nameable, promised by the overload, and has nothing to build.
|
|
27705
|
+
* `VersionTooOldError` has no counterpart here: `MIN_SUPPORTED_VERSION` is
|
|
27706
|
+
* derived from the same list that constrains the type, so nothing nameable is
|
|
27707
|
+
* below it.
|
|
27708
|
+
*
|
|
27709
|
+
* @typeParam V - the version named in `opts.version`, when one is. The returned
|
|
27710
|
+
* surface is `ApiDirectoryByVersion[V]`, so it is derived rather than chosen.
|
|
27572
27711
|
* @typeParam D - the generated API surface the client is typed against, as a
|
|
27573
|
-
* whole (`call`, `job`, `event`)
|
|
27574
|
-
*
|
|
27712
|
+
* whole (`call`, `job`, `event`), for the discovery path where no version is
|
|
27713
|
+
* named. Every verb resolves method names against it, so naming a method this
|
|
27714
|
+
* surface does not have is a build error.
|
|
27575
27715
|
* @returns a Promise that resolves with the created client, or rejects with a
|
|
27576
27716
|
* {@link VersionDiscoveryError} subclass (or a client-selection error).
|
|
27577
27717
|
* Rejects if version discovery on all hostnames *fails* and is not recoverable.
|
|
@@ -27581,6 +27721,9 @@ interface CreateClientOptions {
|
|
|
27581
27721
|
* bug. A network error alongside a version-compatibility error or a 404 does
|
|
27582
27722
|
* *not* reach the fallback — see `selectRepresentativeFailure`.
|
|
27583
27723
|
*/
|
|
27724
|
+
declare function createTrueNasClient<V extends SupportedApiVersion>(opts: CreateClientOptions & {
|
|
27725
|
+
version: V;
|
|
27726
|
+
}): Promise<TrueNasApiClient<DerivedDirectory<V>>>;
|
|
27584
27727
|
declare function createTrueNasClient<D extends ApiDirectoryShape = DefaultApiDirectory>(opts: CreateClientOptions): Promise<TrueNasApiClient<D>>;
|
|
27585
27728
|
|
|
27586
27729
|
/**
|
|
@@ -27608,6 +27751,7 @@ declare function createTrueNasClient<D extends ApiDirectoryShape = DefaultApiDir
|
|
|
27608
27751
|
* - containerStart → virt.instance.start (emits Job updates)
|
|
27609
27752
|
* - containerStop → virt.instance.stop (emits Job updates)
|
|
27610
27753
|
* - containerRestart → virt.instance.restart (emits Job updates)
|
|
27754
|
+
* - containerDelete → virt.instance.delete (already a job; takes no options)
|
|
27611
27755
|
*/
|
|
27612
27756
|
declare class TrueNasApiClientV2510 extends TrueNasApiClient<ApiDirectory$7> {
|
|
27613
27757
|
/**
|
|
@@ -27645,6 +27789,7 @@ declare class TrueNasApiClientV2510 extends TrueNasApiClient<ApiDirectory$7> {
|
|
|
27645
27789
|
* - containerStart → container.start (synchronous, emits null)
|
|
27646
27790
|
* - containerStop → container.stop (emits Job updates)
|
|
27647
27791
|
* - containerRestart → container.stop + container.start (emits Job, then null)
|
|
27792
|
+
* - containerDelete → container.delete (a job since v26.0.0; force/recursive)
|
|
27648
27793
|
*/
|
|
27649
27794
|
declare class TrueNasApiClientV26 extends TrueNasApiClient<ApiDirectory$1> {
|
|
27650
27795
|
/**
|
|
@@ -27692,6 +27837,7 @@ declare class TrueNasApiClientV26 extends TrueNasApiClient<ApiDirectory$1> {
|
|
|
27692
27837
|
* - containerStart → container.start (synchronous, emits null)
|
|
27693
27838
|
* - containerStop → container.stop (emits Job updates)
|
|
27694
27839
|
* - containerRestart → container.stop + container.start (emits Job, then null)
|
|
27840
|
+
* - containerDelete → container.delete (a job since v26.0.0; force/recursive)
|
|
27695
27841
|
*
|
|
27696
27842
|
* Those four are currently identical to v26's, because v27 inherits all three
|
|
27697
27843
|
* container entries the facade touches rather than re-declaring them. Asserted
|
package/dist/index.d.ts
CHANGED
|
@@ -13015,6 +13015,33 @@ interface ContainerStopOptions$1 {
|
|
|
13015
13015
|
timeout?: number;
|
|
13016
13016
|
force: boolean;
|
|
13017
13017
|
}
|
|
13018
|
+
/**
|
|
13019
|
+
* Options for deleting a container (unified interface)
|
|
13020
|
+
*
|
|
13021
|
+
* Both are optional and both default to off, matching middleware. Neither has a
|
|
13022
|
+
* counterpart on v25.10 — `virt.instance.delete` takes an id and nothing else —
|
|
13023
|
+
* so the v25.10 client cannot honour them; it says so rather than dropping them
|
|
13024
|
+
* quietly, because `recursive` in particular destroys data.
|
|
13025
|
+
*/
|
|
13026
|
+
interface ContainerDeleteOptions$1 {
|
|
13027
|
+
/**
|
|
13028
|
+
* Stop the container first if it is not already stopped. Without it, v26+
|
|
13029
|
+
* refuses to delete a running or suspended container rather than tearing it
|
|
13030
|
+
* down underneath itself.
|
|
13031
|
+
*/
|
|
13032
|
+
force?: boolean;
|
|
13033
|
+
/**
|
|
13034
|
+
* Destroy the container's dataset together with its child datasets and
|
|
13035
|
+
* snapshots, any clones of those snapshots wherever they live in the pool,
|
|
13036
|
+
* and any holds on them.
|
|
13037
|
+
*
|
|
13038
|
+
* Releasing a hold can break a replication task that depends on it, and none
|
|
13039
|
+
* of what this destroys is recoverable. Without it, v26+ refuses to delete a
|
|
13040
|
+
* container whose dataset has children or snapshots — which is the refusal
|
|
13041
|
+
* this option exists to override, deliberately.
|
|
13042
|
+
*/
|
|
13043
|
+
recursive?: boolean;
|
|
13044
|
+
}
|
|
13018
13045
|
/**
|
|
13019
13046
|
* Options for restarting a container (unified interface)
|
|
13020
13047
|
*/
|
|
@@ -13058,8 +13085,14 @@ interface ContainerRestartOptions {
|
|
|
13058
13085
|
*
|
|
13059
13086
|
* To add new operations:
|
|
13060
13087
|
* 1. Add the method signature here
|
|
13061
|
-
* 2. Implement in
|
|
13062
|
-
*
|
|
13088
|
+
* 2. Implement it in every client's `createOperations()` —
|
|
13089
|
+
* `TrueNasApiClientV2510`, `TrueNasApiClientV26`, `TrueNasApiClientV27`
|
|
13090
|
+
*
|
|
13091
|
+
* This list used to name only v25.10 and v26, which is how a new operation
|
|
13092
|
+
* would have quietly missed v27. It is not the real safety net either: adding a
|
|
13093
|
+
* member here fails to compile in every client that has not implemented it, and
|
|
13094
|
+
* that is what actually enumerates them. Keep the list current, but trust the
|
|
13095
|
+
* compiler.
|
|
13063
13096
|
*/
|
|
13064
13097
|
interface OperationMappings {
|
|
13065
13098
|
/**
|
|
@@ -13086,6 +13119,22 @@ interface OperationMappings {
|
|
|
13086
13119
|
* - v26+: Emits Job updates (stop phase), then null (sync start)
|
|
13087
13120
|
*/
|
|
13088
13121
|
containerRestart: (id: string, options: ContainerRestartOptions) => Observable<Job | null>;
|
|
13122
|
+
/**
|
|
13123
|
+
* Delete a container
|
|
13124
|
+
* - v25.10: `virt.instance.delete`, already a job — emits Job updates
|
|
13125
|
+
* - v26+: `container.delete`, made a job in v26.0.0 — emits Job updates
|
|
13126
|
+
*
|
|
13127
|
+
* A job on every supported version, so unlike `containerStart` this one does
|
|
13128
|
+
* not change shape across them. It is exposed here because the alternative is
|
|
13129
|
+
* a caller reaching for `api.call('container.delete', …)`, which is the wrong
|
|
13130
|
+
* verb: the method moved out of the call directory when middleware made it a
|
|
13131
|
+
* job, so that does not compile on v26+ and would not track the job if it did.
|
|
13132
|
+
*
|
|
13133
|
+
* `options` are honoured on v26+ only. v25.10's `virt.instance.delete` takes
|
|
13134
|
+
* an id and nothing else; passing them there is logged rather than silently
|
|
13135
|
+
* ignored, because `recursive` destroys data that cannot be recovered.
|
|
13136
|
+
*/
|
|
13137
|
+
containerDelete: (id: string, options?: ContainerDeleteOptions$1) => Observable<Job | null>;
|
|
13089
13138
|
}
|
|
13090
13139
|
|
|
13091
13140
|
/**
|
|
@@ -27510,6 +27559,19 @@ declare const SUPPORTED_API_VERSIONS: readonly ["v25.10.0", "v25.10.1", "v25.10.
|
|
|
27510
27559
|
* how the missing 22 methods went unnoticed in the first place.
|
|
27511
27560
|
*/
|
|
27512
27561
|
type DefaultApiDirectory = ApiDirectory$7;
|
|
27562
|
+
/**
|
|
27563
|
+
* The surface a named version derives, falling back when nothing was narrowed.
|
|
27564
|
+
*
|
|
27565
|
+
* `V` only pins a directory when inference narrowed it. A wrapper typed
|
|
27566
|
+
* `(version: SupportedApiVersion)` widens it back to the whole union, and
|
|
27567
|
+
* indexing by a union yields a union of directories whose usable methods are
|
|
27568
|
+
* their *intersection* — narrower than the default surface, so naming the
|
|
27569
|
+
* version would buy fewer methods than naming nothing. That case falls back to
|
|
27570
|
+
* {@link DefaultApiDirectory} instead, matching every other shape that loses
|
|
27571
|
+
* the literal. A partial union still derives: methods common to the versions
|
|
27572
|
+
* named is the right answer for "one of these".
|
|
27573
|
+
*/
|
|
27574
|
+
type DerivedDirectory<V extends SupportedApiVersion> = SupportedApiVersion extends V ? DefaultApiDirectory : ApiDirectoryByVersion[V];
|
|
27513
27575
|
/** Options for {@link createTrueNasClient}. */
|
|
27514
27576
|
interface CreateClientOptions {
|
|
27515
27577
|
/** System UUID. */
|
|
@@ -27533,12 +27595,22 @@ interface CreateClientOptions {
|
|
|
27533
27595
|
* through the client, to the connection.
|
|
27534
27596
|
*/
|
|
27535
27597
|
logger?: Logger;
|
|
27598
|
+
/**
|
|
27599
|
+
* The appliance's API version, when the caller already knows it.
|
|
27600
|
+
*
|
|
27601
|
+
* Supplying it skips version discovery entirely — no `GET /api/versions`, no
|
|
27602
|
+
* CORS fallback — and *derives* the client's typed surface from the string,
|
|
27603
|
+
* so `version: 'v27.0.0'` yields `TrueNasApiClient<ApiDirectoryV27_0_0>`
|
|
27604
|
+
* without the caller asserting it through a type parameter.
|
|
27605
|
+
*/
|
|
27606
|
+
version?: SupportedApiVersion;
|
|
27536
27607
|
}
|
|
27537
27608
|
/**
|
|
27538
27609
|
* Creates a version-specific TrueNAS API client.
|
|
27539
27610
|
*
|
|
27540
27611
|
* 1. Discovers the API version (`GET /api/versions`), asking every hostname in
|
|
27541
|
-
* parallel. The first usable answer wins.
|
|
27612
|
+
* parallel. The first usable answer wins — *unless* `opts.version` says
|
|
27613
|
+
* which version this is, in which case discovery is skipped entirely.
|
|
27542
27614
|
* 2. Selects the matching client implementation (`v25.10.x` -> `TrueNasApiClientV2510`,
|
|
27543
27615
|
* `v26.x.y` -> `TrueNasApiClientV26`, `v27.x.y` -> `TrueNasApiClientV27`).
|
|
27544
27616
|
* 3. Instantiates and returns it.
|
|
@@ -27569,9 +27641,77 @@ interface CreateClientOptions {
|
|
|
27569
27641
|
* supported version. Operations that must work across versions belong on
|
|
27570
27642
|
* `client.ops`, which resolves them at runtime.
|
|
27571
27643
|
*
|
|
27644
|
+
* ## Naming the version instead
|
|
27645
|
+
*
|
|
27646
|
+
* A caller that already knows its appliance — a UI served by the appliance
|
|
27647
|
+
* itself, a test harness against a pinned image — can say so and skip discovery
|
|
27648
|
+
* altogether:
|
|
27649
|
+
*
|
|
27650
|
+
* ```typescript
|
|
27651
|
+
* const client = await createTrueNasClient({
|
|
27652
|
+
* uuid, hostnames, enabled: true, version: 'v27.0.0',
|
|
27653
|
+
* });
|
|
27654
|
+
* // client: TrueNasApiClient<ApiDirectoryV27_0_0>, derived from the string
|
|
27655
|
+
* ```
|
|
27656
|
+
*
|
|
27657
|
+
* The surface is *derived* rather than asserted: `ApiDirectoryByVersion` maps
|
|
27658
|
+
* the version string to its directory, so the caller writes no cast and names
|
|
27659
|
+
* no directory type. A version this package ships no types for does not
|
|
27660
|
+
* compile.
|
|
27661
|
+
*
|
|
27662
|
+
* **The derivation needs the version to be literal at the call site.** It comes
|
|
27663
|
+
* from inference on `{ version: V }`, so it holds for a string literal written
|
|
27664
|
+
* in the options object (or a `const`-typed one). It does not survive
|
|
27665
|
+
* indirection:
|
|
27666
|
+
*
|
|
27667
|
+
* - `createTrueNasClient<D>({ …, version: 'v27.0.0' })` — an explicit type
|
|
27668
|
+
* argument makes the derived overload inapplicable, so `D` wins.
|
|
27669
|
+
* - `(v?: SupportedApiVersion) => createTrueNasClient({ …, version: v })` — the
|
|
27670
|
+
* property is `SupportedApiVersion | undefined`, which no `V` satisfies.
|
|
27671
|
+
* - `(v: SupportedApiVersion) => …` — reaches this overload, but `V` widens to
|
|
27672
|
+
* the whole union, which derives nothing.
|
|
27673
|
+
* - `const opts: CreateClientOptions = { …, version: 'v27.0.0' }` — the
|
|
27674
|
+
* annotation widens the property before the call sees it.
|
|
27675
|
+
*
|
|
27676
|
+
* All of them compile, run against the named version, and type as
|
|
27677
|
+
* {@link DefaultApiDirectory}. That fails in the safe direction — understated
|
|
27678
|
+
* types give a compile error at the method call rather than a runtime surprise —
|
|
27679
|
+
* but it is silent, so a wrapper that forwards a version gets none of the
|
|
27680
|
+
* surface it named. Keep the literal at the call site, and if you are adding
|
|
27681
|
+
* `version` to an existing `createTrueNasClient<ApiDirectoryV26_0_0>(opts)`
|
|
27682
|
+
* call, delete the type argument in the same edit.
|
|
27683
|
+
*
|
|
27684
|
+
* Two consequences worth knowing before reaching for it.
|
|
27685
|
+
*
|
|
27686
|
+
* It is a stronger claim than `D` alone, because it also picks the websocket
|
|
27687
|
+
* path. Naming `v27.0.0` at a v26 appliance connects on `/api/v27.0.0` with v27
|
|
27688
|
+
* types over a v26 server, and discovery cannot correct it — declining
|
|
27689
|
+
* discovery is the whole point. `D` on its own only mistyped the surface; this
|
|
27690
|
+
* mistypes the surface *and* dials the wrong number.
|
|
27691
|
+
*
|
|
27692
|
+
* Compatibility is still checked. Skipping discovery skips the network round
|
|
27693
|
+
* trip, not the range check, which is local and free. Two refusals reach a
|
|
27694
|
+
* caller, and they are not interchangeable:
|
|
27695
|
+
*
|
|
27696
|
+
* - a string that is not a `SupportedApiVersion` at all — only reachable from
|
|
27697
|
+
* JavaScript — throws a plain `Error` naming the versions that are.
|
|
27698
|
+
* - a version this package ships types for but cannot build a client for
|
|
27699
|
+
* throws {@link VersionTooNewError}, the same error discovery raises.
|
|
27700
|
+
*
|
|
27701
|
+
* The second is not hypothetical. `MAX_SUPPORTED_VERSION` is a hand-written
|
|
27702
|
+
* literal, and while it matches the newest generated version today, a
|
|
27703
|
+
* regeneration can add a year before anyone writes its client — at which point
|
|
27704
|
+
* that version is nameable, promised by the overload, and has nothing to build.
|
|
27705
|
+
* `VersionTooOldError` has no counterpart here: `MIN_SUPPORTED_VERSION` is
|
|
27706
|
+
* derived from the same list that constrains the type, so nothing nameable is
|
|
27707
|
+
* below it.
|
|
27708
|
+
*
|
|
27709
|
+
* @typeParam V - the version named in `opts.version`, when one is. The returned
|
|
27710
|
+
* surface is `ApiDirectoryByVersion[V]`, so it is derived rather than chosen.
|
|
27572
27711
|
* @typeParam D - the generated API surface the client is typed against, as a
|
|
27573
|
-
* whole (`call`, `job`, `event`)
|
|
27574
|
-
*
|
|
27712
|
+
* whole (`call`, `job`, `event`), for the discovery path where no version is
|
|
27713
|
+
* named. Every verb resolves method names against it, so naming a method this
|
|
27714
|
+
* surface does not have is a build error.
|
|
27575
27715
|
* @returns a Promise that resolves with the created client, or rejects with a
|
|
27576
27716
|
* {@link VersionDiscoveryError} subclass (or a client-selection error).
|
|
27577
27717
|
* Rejects if version discovery on all hostnames *fails* and is not recoverable.
|
|
@@ -27581,6 +27721,9 @@ interface CreateClientOptions {
|
|
|
27581
27721
|
* bug. A network error alongside a version-compatibility error or a 404 does
|
|
27582
27722
|
* *not* reach the fallback — see `selectRepresentativeFailure`.
|
|
27583
27723
|
*/
|
|
27724
|
+
declare function createTrueNasClient<V extends SupportedApiVersion>(opts: CreateClientOptions & {
|
|
27725
|
+
version: V;
|
|
27726
|
+
}): Promise<TrueNasApiClient<DerivedDirectory<V>>>;
|
|
27584
27727
|
declare function createTrueNasClient<D extends ApiDirectoryShape = DefaultApiDirectory>(opts: CreateClientOptions): Promise<TrueNasApiClient<D>>;
|
|
27585
27728
|
|
|
27586
27729
|
/**
|
|
@@ -27608,6 +27751,7 @@ declare function createTrueNasClient<D extends ApiDirectoryShape = DefaultApiDir
|
|
|
27608
27751
|
* - containerStart → virt.instance.start (emits Job updates)
|
|
27609
27752
|
* - containerStop → virt.instance.stop (emits Job updates)
|
|
27610
27753
|
* - containerRestart → virt.instance.restart (emits Job updates)
|
|
27754
|
+
* - containerDelete → virt.instance.delete (already a job; takes no options)
|
|
27611
27755
|
*/
|
|
27612
27756
|
declare class TrueNasApiClientV2510 extends TrueNasApiClient<ApiDirectory$7> {
|
|
27613
27757
|
/**
|
|
@@ -27645,6 +27789,7 @@ declare class TrueNasApiClientV2510 extends TrueNasApiClient<ApiDirectory$7> {
|
|
|
27645
27789
|
* - containerStart → container.start (synchronous, emits null)
|
|
27646
27790
|
* - containerStop → container.stop (emits Job updates)
|
|
27647
27791
|
* - containerRestart → container.stop + container.start (emits Job, then null)
|
|
27792
|
+
* - containerDelete → container.delete (a job since v26.0.0; force/recursive)
|
|
27648
27793
|
*/
|
|
27649
27794
|
declare class TrueNasApiClientV26 extends TrueNasApiClient<ApiDirectory$1> {
|
|
27650
27795
|
/**
|
|
@@ -27692,6 +27837,7 @@ declare class TrueNasApiClientV26 extends TrueNasApiClient<ApiDirectory$1> {
|
|
|
27692
27837
|
* - containerStart → container.start (synchronous, emits null)
|
|
27693
27838
|
* - containerStop → container.stop (emits Job updates)
|
|
27694
27839
|
* - containerRestart → container.stop + container.start (emits Job, then null)
|
|
27840
|
+
* - containerDelete → container.delete (a job since v26.0.0; force/recursive)
|
|
27695
27841
|
*
|
|
27696
27842
|
* Those four are currently identical to v26's, because v27 inherits all three
|
|
27697
27843
|
* container entries the facade touches rather than re-declaring them. Asserted
|
package/dist/index.js
CHANGED
|
@@ -3148,7 +3148,29 @@ var TrueNasApiClientV2510 = class extends TrueNasApiClient {
|
|
|
3148
3148
|
containerQuery: () => this.api.query("virt.instance.query", [["type", "=", "CONTAINER"]]).pipe(map((instances) => instances.map(toContainer))),
|
|
3149
3149
|
containerStart: (id) => this.api.job("virt.instance.start", [id]),
|
|
3150
3150
|
containerStop: (id, options) => this.api.job("virt.instance.stop", [id, options]),
|
|
3151
|
-
containerRestart: (id, options) => this.api.job("virt.instance.restart", [id, options])
|
|
3151
|
+
containerRestart: (id, options) => this.api.job("virt.instance.restart", [id, options]),
|
|
3152
|
+
// Already a job here — `virt.instance.delete` has been one since
|
|
3153
|
+
// v25.10.0 — so this needs no synthesis, only the id. It takes nothing
|
|
3154
|
+
// else: there is no `force` and no `recursive` on this version.
|
|
3155
|
+
//
|
|
3156
|
+
// Unsupported options are reported rather than dropped. `recursive`
|
|
3157
|
+
// destroys child datasets, snapshots and clones irrecoverably, so a
|
|
3158
|
+
// caller who asked for it and silently did not get it has been told
|
|
3159
|
+
// something false about what just happened to their data. Reporting is
|
|
3160
|
+
// all this layer can do — refusing outright would make `ops.containerDelete`
|
|
3161
|
+
// unusable on v25.10 for the ordinary case, which is the case that works.
|
|
3162
|
+
containerDelete: (id, options) => {
|
|
3163
|
+
const unsupported = ["force", "recursive"].filter(
|
|
3164
|
+
(key) => options?.[key]
|
|
3165
|
+
);
|
|
3166
|
+
if (unsupported.length > 0) {
|
|
3167
|
+
this.logger.warn(
|
|
3168
|
+
"containerDelete: v25.10 has no counterpart for these options and will delete without them",
|
|
3169
|
+
{ ignored: unsupported, id, method: "virt.instance.delete" }
|
|
3170
|
+
);
|
|
3171
|
+
}
|
|
3172
|
+
return this.api.job("virt.instance.delete", [id]);
|
|
3173
|
+
}
|
|
3152
3174
|
};
|
|
3153
3175
|
}
|
|
3154
3176
|
};
|
|
@@ -3216,7 +3238,26 @@ var TrueNasApiClientV26 = class extends TrueNasApiClient {
|
|
|
3216
3238
|
)
|
|
3217
3239
|
)
|
|
3218
3240
|
);
|
|
3219
|
-
}
|
|
3241
|
+
},
|
|
3242
|
+
// A job since v26.0.0 — middleware made deletion long-running (it stops
|
|
3243
|
+
// the container when asked, tears down the libvirt domain and destroys
|
|
3244
|
+
// the dataset), and the generated directory moved it out of `call`
|
|
3245
|
+
// accordingly. `api.job` is what tracks it; `api.call` would not compile.
|
|
3246
|
+
//
|
|
3247
|
+
// Options pass straight through when given: the unified
|
|
3248
|
+
// `ContainerDeleteOptions` is `force`/`recursive`, exactly what the
|
|
3249
|
+
// generated params take.
|
|
3250
|
+
//
|
|
3251
|
+
// When they are not given the argument is *omitted* rather than passed as
|
|
3252
|
+
// `undefined`. `JSON.stringify` renders a trailing `undefined` array
|
|
3253
|
+
// element as `null`, and middleware declares `options: ContainerDeleteOptions`
|
|
3254
|
+
// with a model default and no `| None` — so `[id, null]` is a validation
|
|
3255
|
+
// error rather than "use the defaults", which is the one thing a caller
|
|
3256
|
+
// passing nothing is asking for.
|
|
3257
|
+
containerDelete: (id, options) => this.api.job(
|
|
3258
|
+
"container.delete",
|
|
3259
|
+
options ? [parseInt(id, 10), options] : [parseInt(id, 10)]
|
|
3260
|
+
)
|
|
3220
3261
|
};
|
|
3221
3262
|
}
|
|
3222
3263
|
};
|
|
@@ -3278,7 +3319,26 @@ var TrueNasApiClientV27 = class extends TrueNasApiClient {
|
|
|
3278
3319
|
)
|
|
3279
3320
|
)
|
|
3280
3321
|
);
|
|
3281
|
-
}
|
|
3322
|
+
},
|
|
3323
|
+
// A job since v26.0.0 — middleware made deletion long-running (it stops
|
|
3324
|
+
// the container when asked, tears down the libvirt domain and destroys
|
|
3325
|
+
// the dataset), and the generated directory moved it out of `call`
|
|
3326
|
+
// accordingly. `api.job` is what tracks it; `api.call` would not compile.
|
|
3327
|
+
//
|
|
3328
|
+
// Options pass straight through when given: the unified
|
|
3329
|
+
// `ContainerDeleteOptions` is `force`/`recursive`, exactly what the
|
|
3330
|
+
// generated params take.
|
|
3331
|
+
//
|
|
3332
|
+
// When they are not given the argument is *omitted* rather than passed as
|
|
3333
|
+
// `undefined`. `JSON.stringify` renders a trailing `undefined` array
|
|
3334
|
+
// element as `null`, and middleware declares `options: ContainerDeleteOptions`
|
|
3335
|
+
// with a model default and no `| None` — so `[id, null]` is a validation
|
|
3336
|
+
// error rather than "use the defaults", which is the one thing a caller
|
|
3337
|
+
// passing nothing is asking for.
|
|
3338
|
+
containerDelete: (id, options) => this.api.job(
|
|
3339
|
+
"container.delete",
|
|
3340
|
+
options ? [parseInt(id, 10), options] : [parseInt(id, 10)]
|
|
3341
|
+
)
|
|
3282
3342
|
};
|
|
3283
3343
|
}
|
|
3284
3344
|
};
|
|
@@ -3582,12 +3642,40 @@ async function createTrueNasClient(opts) {
|
|
|
3582
3642
|
`Cannot create client for system ${uuid}: hostnames array is empty`
|
|
3583
3643
|
);
|
|
3584
3644
|
}
|
|
3585
|
-
const versionDiscovery = new VersionDiscovery(logger);
|
|
3586
3645
|
logger.info("Creating versioned API client", {
|
|
3587
3646
|
uuid: uuid.slice(0, 8),
|
|
3588
3647
|
hostnames: hostnames.join(", "),
|
|
3589
3648
|
systemName
|
|
3590
3649
|
});
|
|
3650
|
+
if (opts.version !== void 0) {
|
|
3651
|
+
if (!SUPPORTED_API_VERSIONS.includes(opts.version)) {
|
|
3652
|
+
throw new Error(
|
|
3653
|
+
`Cannot create client for system ${uuid}: '${opts.version}' is not a version this package ships types for. Supported: ${SUPPORTED_API_VERSIONS.join(", ")}.`
|
|
3654
|
+
);
|
|
3655
|
+
}
|
|
3656
|
+
const known = parseApiVersion(opts.version);
|
|
3657
|
+
if (!known) {
|
|
3658
|
+
throw new Error(
|
|
3659
|
+
`Cannot create client for system ${uuid}: supported version '${opts.version}' failed to parse.`
|
|
3660
|
+
);
|
|
3661
|
+
}
|
|
3662
|
+
const compatibility = checkVersionCompatibility(known);
|
|
3663
|
+
if (compatibility === "too-new" /* TooNew */) {
|
|
3664
|
+
throw new VersionTooNewError(hostnames[0], [known.version]);
|
|
3665
|
+
}
|
|
3666
|
+
if (compatibility !== "compatible" /* Compatible */) {
|
|
3667
|
+
throw new Error(
|
|
3668
|
+
`Cannot create client for system ${uuid}: the supported version range is not usable (${apiVersionConfig.MIN_SUPPORTED_VERSION}..${apiVersionConfig.MAX_SUPPORTED_VERSION}).`
|
|
3669
|
+
);
|
|
3670
|
+
}
|
|
3671
|
+
logger.info("API version supplied by the caller, skipping discovery", {
|
|
3672
|
+
uuid: uuid.slice(0, 8),
|
|
3673
|
+
version: known.version,
|
|
3674
|
+
websocketPath: known.websocketPath
|
|
3675
|
+
});
|
|
3676
|
+
return instantiateClientForVersion(known, opts, logger);
|
|
3677
|
+
}
|
|
3678
|
+
const versionDiscovery = new VersionDiscovery(logger);
|
|
3591
3679
|
let version;
|
|
3592
3680
|
try {
|
|
3593
3681
|
const winner = await discoverVersionFromAnyHostname(
|