@truenas/api-client 3.0.2 → 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 +29 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +97 -3
- package/dist/index.d.ts +97 -3
- package/dist/index.js +29 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -27559,6 +27559,19 @@ declare const SUPPORTED_API_VERSIONS: readonly ["v25.10.0", "v25.10.1", "v25.10.
|
|
|
27559
27559
|
* how the missing 22 methods went unnoticed in the first place.
|
|
27560
27560
|
*/
|
|
27561
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];
|
|
27562
27575
|
/** Options for {@link createTrueNasClient}. */
|
|
27563
27576
|
interface CreateClientOptions {
|
|
27564
27577
|
/** System UUID. */
|
|
@@ -27582,12 +27595,22 @@ interface CreateClientOptions {
|
|
|
27582
27595
|
* through the client, to the connection.
|
|
27583
27596
|
*/
|
|
27584
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;
|
|
27585
27607
|
}
|
|
27586
27608
|
/**
|
|
27587
27609
|
* Creates a version-specific TrueNAS API client.
|
|
27588
27610
|
*
|
|
27589
27611
|
* 1. Discovers the API version (`GET /api/versions`), asking every hostname in
|
|
27590
|
-
* 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.
|
|
27591
27614
|
* 2. Selects the matching client implementation (`v25.10.x` -> `TrueNasApiClientV2510`,
|
|
27592
27615
|
* `v26.x.y` -> `TrueNasApiClientV26`, `v27.x.y` -> `TrueNasApiClientV27`).
|
|
27593
27616
|
* 3. Instantiates and returns it.
|
|
@@ -27618,9 +27641,77 @@ interface CreateClientOptions {
|
|
|
27618
27641
|
* supported version. Operations that must work across versions belong on
|
|
27619
27642
|
* `client.ops`, which resolves them at runtime.
|
|
27620
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.
|
|
27621
27711
|
* @typeParam D - the generated API surface the client is typed against, as a
|
|
27622
|
-
* whole (`call`, `job`, `event`)
|
|
27623
|
-
*
|
|
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.
|
|
27624
27715
|
* @returns a Promise that resolves with the created client, or rejects with a
|
|
27625
27716
|
* {@link VersionDiscoveryError} subclass (or a client-selection error).
|
|
27626
27717
|
* Rejects if version discovery on all hostnames *fails* and is not recoverable.
|
|
@@ -27630,6 +27721,9 @@ interface CreateClientOptions {
|
|
|
27630
27721
|
* bug. A network error alongside a version-compatibility error or a 404 does
|
|
27631
27722
|
* *not* reach the fallback — see `selectRepresentativeFailure`.
|
|
27632
27723
|
*/
|
|
27724
|
+
declare function createTrueNasClient<V extends SupportedApiVersion>(opts: CreateClientOptions & {
|
|
27725
|
+
version: V;
|
|
27726
|
+
}): Promise<TrueNasApiClient<DerivedDirectory<V>>>;
|
|
27633
27727
|
declare function createTrueNasClient<D extends ApiDirectoryShape = DefaultApiDirectory>(opts: CreateClientOptions): Promise<TrueNasApiClient<D>>;
|
|
27634
27728
|
|
|
27635
27729
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -27559,6 +27559,19 @@ declare const SUPPORTED_API_VERSIONS: readonly ["v25.10.0", "v25.10.1", "v25.10.
|
|
|
27559
27559
|
* how the missing 22 methods went unnoticed in the first place.
|
|
27560
27560
|
*/
|
|
27561
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];
|
|
27562
27575
|
/** Options for {@link createTrueNasClient}. */
|
|
27563
27576
|
interface CreateClientOptions {
|
|
27564
27577
|
/** System UUID. */
|
|
@@ -27582,12 +27595,22 @@ interface CreateClientOptions {
|
|
|
27582
27595
|
* through the client, to the connection.
|
|
27583
27596
|
*/
|
|
27584
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;
|
|
27585
27607
|
}
|
|
27586
27608
|
/**
|
|
27587
27609
|
* Creates a version-specific TrueNAS API client.
|
|
27588
27610
|
*
|
|
27589
27611
|
* 1. Discovers the API version (`GET /api/versions`), asking every hostname in
|
|
27590
|
-
* 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.
|
|
27591
27614
|
* 2. Selects the matching client implementation (`v25.10.x` -> `TrueNasApiClientV2510`,
|
|
27592
27615
|
* `v26.x.y` -> `TrueNasApiClientV26`, `v27.x.y` -> `TrueNasApiClientV27`).
|
|
27593
27616
|
* 3. Instantiates and returns it.
|
|
@@ -27618,9 +27641,77 @@ interface CreateClientOptions {
|
|
|
27618
27641
|
* supported version. Operations that must work across versions belong on
|
|
27619
27642
|
* `client.ops`, which resolves them at runtime.
|
|
27620
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.
|
|
27621
27711
|
* @typeParam D - the generated API surface the client is typed against, as a
|
|
27622
|
-
* whole (`call`, `job`, `event`)
|
|
27623
|
-
*
|
|
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.
|
|
27624
27715
|
* @returns a Promise that resolves with the created client, or rejects with a
|
|
27625
27716
|
* {@link VersionDiscoveryError} subclass (or a client-selection error).
|
|
27626
27717
|
* Rejects if version discovery on all hostnames *fails* and is not recoverable.
|
|
@@ -27630,6 +27721,9 @@ interface CreateClientOptions {
|
|
|
27630
27721
|
* bug. A network error alongside a version-compatibility error or a 404 does
|
|
27631
27722
|
* *not* reach the fallback — see `selectRepresentativeFailure`.
|
|
27632
27723
|
*/
|
|
27724
|
+
declare function createTrueNasClient<V extends SupportedApiVersion>(opts: CreateClientOptions & {
|
|
27725
|
+
version: V;
|
|
27726
|
+
}): Promise<TrueNasApiClient<DerivedDirectory<V>>>;
|
|
27633
27727
|
declare function createTrueNasClient<D extends ApiDirectoryShape = DefaultApiDirectory>(opts: CreateClientOptions): Promise<TrueNasApiClient<D>>;
|
|
27634
27728
|
|
|
27635
27729
|
/**
|
package/dist/index.js
CHANGED
|
@@ -3642,12 +3642,40 @@ async function createTrueNasClient(opts) {
|
|
|
3642
3642
|
`Cannot create client for system ${uuid}: hostnames array is empty`
|
|
3643
3643
|
);
|
|
3644
3644
|
}
|
|
3645
|
-
const versionDiscovery = new VersionDiscovery(logger);
|
|
3646
3645
|
logger.info("Creating versioned API client", {
|
|
3647
3646
|
uuid: uuid.slice(0, 8),
|
|
3648
3647
|
hostnames: hostnames.join(", "),
|
|
3649
3648
|
systemName
|
|
3650
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);
|
|
3651
3679
|
let version;
|
|
3652
3680
|
try {
|
|
3653
3681
|
const winner = await discoverVersionFromAnyHostname(
|