@truenas/api-client 8.0.0 → 9.0.1
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 +37 -8
- package/dist/{chunk-RH7UXULE.js → chunk-K7BL6J3Z.js} +313 -81
- package/dist/chunk-K7BL6J3Z.js.map +1 -0
- package/dist/{chunk-DIZAGV4B.cjs → chunk-U6V2Y3AE.cjs} +315 -81
- package/dist/chunk-U6V2Y3AE.cjs.map +1 -0
- package/dist/index.cjs +44 -40
- package/dist/index.d.cts +68 -21
- package/dist/index.d.ts +68 -21
- package/dist/index.js +1 -1
- package/dist/testing/index.cjs +28 -16
- package/dist/testing/index.cjs.map +1 -1
- package/dist/testing/index.d.cts +15 -2
- package/dist/testing/index.d.ts +15 -2
- package/dist/testing/index.js +18 -6
- package/dist/testing/index.js.map +1 -1
- package/dist/{truenas-auth-mechanism.enum-DUitTxen.d.cts → truenas-auth-mechanism.enum-Bp2XMMwK.d.cts} +596 -122
- package/dist/{truenas-auth-mechanism.enum-DUitTxen.d.ts → truenas-auth-mechanism.enum-Bp2XMMwK.d.ts} +596 -122
- package/package.json +2 -2
- package/dist/chunk-DIZAGV4B.cjs.map +0 -1
- package/dist/chunk-RH7UXULE.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BehaviorSubject, filter, switchMap, catchError, EMPTY, tap, map, finalize, take, Subject, distinctUntilChanged, of, startWith, concat, throwError, retry, race, timer, skip, takeUntil, shareReplay, interval, Observable, timeout, TimeoutError, toArray, from, defer, firstValueFrom, share, merge, takeWhile } from 'rxjs';
|
|
1
|
+
import { BehaviorSubject, filter, switchMap, catchError, EMPTY, tap, map, finalize, take, Subject, distinctUntilChanged, of, startWith, concat, throwError, retry, race, timer, skip, takeUntil, shareReplay, interval, Observable, timeout, TimeoutError, toArray, from, defer, firstValueFrom, share, ignoreElements, merge, takeWhile, skipWhile, pairwise } from 'rxjs';
|
|
2
2
|
import { webSocket } from 'rxjs/webSocket';
|
|
3
3
|
import { map as map$1, catchError as catchError$1, shareReplay as shareReplay$1 } from 'rxjs/operators';
|
|
4
4
|
|
|
@@ -28,6 +28,34 @@ function getApiErrorMessage(error, fallback = "API call failed") {
|
|
|
28
28
|
return fallback;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
// src/errors/api.errors.ts
|
|
32
|
+
var TrueNasApiError = class _TrueNasApiError extends Error {
|
|
33
|
+
constructor(frame, method) {
|
|
34
|
+
super(getApiErrorMessage(frame, "API call failed"));
|
|
35
|
+
this.frame = frame;
|
|
36
|
+
this.method = method;
|
|
37
|
+
this.name = "TrueNasApiError";
|
|
38
|
+
Object.setPrototypeOf(this, _TrueNasApiError.prototype);
|
|
39
|
+
}
|
|
40
|
+
/** The JSON-RPC code: `-32001` for a refused call, others for a malformed one. */
|
|
41
|
+
get code() {
|
|
42
|
+
return this.frame.code;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The TrueNAS payload — `errname`, `reason`, `extra`, `trace`.
|
|
46
|
+
*
|
|
47
|
+
* `undefined` for the codes middleware sends without one, such as
|
|
48
|
+
* `-32601 "Method does not exist"`.
|
|
49
|
+
*/
|
|
50
|
+
get data() {
|
|
51
|
+
return this.frame.data;
|
|
52
|
+
}
|
|
53
|
+
/** `data.errname`, e.g. `EINVAL`, or `undefined` when there is no payload. */
|
|
54
|
+
get errname() {
|
|
55
|
+
return this.frame.data?.errname;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
31
59
|
// src/types/job.type.ts
|
|
32
60
|
var JobState = /* @__PURE__ */ ((JobState2) => {
|
|
33
61
|
JobState2["Pending"] = "PENDING";
|
|
@@ -1646,9 +1674,9 @@ __export(v25_10_5_exports, {
|
|
|
1646
1674
|
ZfsTierRewriteJobEntryStatusInput: () => ZfsTierRewriteJobEntryStatusInput
|
|
1647
1675
|
});
|
|
1648
1676
|
|
|
1649
|
-
// src/generated/
|
|
1650
|
-
var
|
|
1651
|
-
__export(
|
|
1677
|
+
// src/generated/v27_0_0/index.ts
|
|
1678
|
+
var v27_0_0_exports = {};
|
|
1679
|
+
__export(v27_0_0_exports, {
|
|
1652
1680
|
ACLTemplateEntryAcltypeInput: () => ACLTemplateEntryAcltypeInput,
|
|
1653
1681
|
Access: () => Access,
|
|
1654
1682
|
Aclmode: () => Aclmode,
|
|
@@ -1781,7 +1809,7 @@ __export(v26_0_0_exports, {
|
|
|
1781
1809
|
ZpoolScrubRunAction: () => ZpoolScrubRunAction
|
|
1782
1810
|
});
|
|
1783
1811
|
|
|
1784
|
-
// src/generated/
|
|
1812
|
+
// src/generated/v27_0_0/api-types.ts
|
|
1785
1813
|
var Access = {
|
|
1786
1814
|
Readonly: "READONLY",
|
|
1787
1815
|
Writeonly: "WRITEONLY",
|
|
@@ -1903,9 +1931,9 @@ var ZpoolScrubRunAction = {
|
|
|
1903
1931
|
Cancel: "CANCEL"
|
|
1904
1932
|
};
|
|
1905
1933
|
|
|
1906
|
-
// src/generated/
|
|
1907
|
-
var
|
|
1908
|
-
__export(
|
|
1934
|
+
// src/generated/v28_0_0/index.ts
|
|
1935
|
+
var v28_0_0_exports = {};
|
|
1936
|
+
__export(v28_0_0_exports, {
|
|
1909
1937
|
ACLTemplateEntryAcltypeInput: () => ACLTemplateEntryAcltypeInput,
|
|
1910
1938
|
Access: () => Access,
|
|
1911
1939
|
Aclmode: () => Aclmode,
|
|
@@ -1928,7 +1956,7 @@ __export(v27_0_0_exports, {
|
|
|
1928
1956
|
CloudTaskAttributesEncryption: () => CloudTaskAttributesEncryption,
|
|
1929
1957
|
CloudTaskAttributesEncryptionInput: () => CloudTaskAttributesEncryptionInput,
|
|
1930
1958
|
Code: () => Code,
|
|
1931
|
-
Compression: () =>
|
|
1959
|
+
Compression: () => Compression2,
|
|
1932
1960
|
ContainerNICDeviceType: () => ContainerNICDeviceType,
|
|
1933
1961
|
ContainerNICDeviceTypeInput: () => ContainerNICDeviceTypeInput,
|
|
1934
1962
|
ContainerStatusState: () => ContainerStatusState,
|
|
@@ -2006,6 +2034,7 @@ __export(v27_0_0_exports, {
|
|
|
2006
2034
|
Snapdir: () => Snapdir,
|
|
2007
2035
|
SourceInput: () => SourceInput,
|
|
2008
2036
|
SourceValueType: () => SourceValueType,
|
|
2037
|
+
SourceValueTypeInput: () => SourceValueTypeInput,
|
|
2009
2038
|
SupportNewTicketCommunityTypeInput: () => SupportNewTicketCommunityTypeInput,
|
|
2010
2039
|
Sync: () => Sync,
|
|
2011
2040
|
SyslogServerTransport: () => SyslogServerTransport,
|
|
@@ -2039,7 +2068,67 @@ __export(v27_0_0_exports, {
|
|
|
2039
2068
|
ZpoolScrubRunAction: () => ZpoolScrubRunAction
|
|
2040
2069
|
});
|
|
2041
2070
|
|
|
2042
|
-
// src/generated/
|
|
2071
|
+
// src/generated/v28_0_0/api-types.ts
|
|
2072
|
+
var Compression2 = {
|
|
2073
|
+
On: "ON",
|
|
2074
|
+
Off: "OFF",
|
|
2075
|
+
Lz4: "LZ4",
|
|
2076
|
+
Gzip: "GZIP",
|
|
2077
|
+
Gzip1: "GZIP-1",
|
|
2078
|
+
Gzip2: "GZIP-2",
|
|
2079
|
+
Gzip3: "GZIP-3",
|
|
2080
|
+
Gzip4: "GZIP-4",
|
|
2081
|
+
Gzip5: "GZIP-5",
|
|
2082
|
+
Gzip6: "GZIP-6",
|
|
2083
|
+
Gzip7: "GZIP-7",
|
|
2084
|
+
Gzip8: "GZIP-8",
|
|
2085
|
+
Gzip9: "GZIP-9",
|
|
2086
|
+
Zstd: "ZSTD",
|
|
2087
|
+
ZstdFast: "ZSTD-FAST",
|
|
2088
|
+
Zle: "ZLE",
|
|
2089
|
+
Lzjb: "LZJB",
|
|
2090
|
+
Zstd1: "ZSTD-1",
|
|
2091
|
+
Zstd2: "ZSTD-2",
|
|
2092
|
+
Zstd3: "ZSTD-3",
|
|
2093
|
+
Zstd4: "ZSTD-4",
|
|
2094
|
+
Zstd5: "ZSTD-5",
|
|
2095
|
+
Zstd6: "ZSTD-6",
|
|
2096
|
+
Zstd7: "ZSTD-7",
|
|
2097
|
+
Zstd8: "ZSTD-8",
|
|
2098
|
+
Zstd9: "ZSTD-9",
|
|
2099
|
+
Zstd10: "ZSTD-10",
|
|
2100
|
+
Zstd11: "ZSTD-11",
|
|
2101
|
+
Zstd12: "ZSTD-12",
|
|
2102
|
+
Zstd13: "ZSTD-13",
|
|
2103
|
+
Zstd14: "ZSTD-14",
|
|
2104
|
+
Zstd15: "ZSTD-15",
|
|
2105
|
+
Zstd16: "ZSTD-16",
|
|
2106
|
+
Zstd17: "ZSTD-17",
|
|
2107
|
+
Zstd18: "ZSTD-18",
|
|
2108
|
+
Zstd19: "ZSTD-19",
|
|
2109
|
+
ZstdFast1: "ZSTD-FAST-1",
|
|
2110
|
+
ZstdFast2: "ZSTD-FAST-2",
|
|
2111
|
+
ZstdFast3: "ZSTD-FAST-3",
|
|
2112
|
+
ZstdFast4: "ZSTD-FAST-4",
|
|
2113
|
+
ZstdFast5: "ZSTD-FAST-5",
|
|
2114
|
+
ZstdFast6: "ZSTD-FAST-6",
|
|
2115
|
+
ZstdFast7: "ZSTD-FAST-7",
|
|
2116
|
+
ZstdFast8: "ZSTD-FAST-8",
|
|
2117
|
+
ZstdFast9: "ZSTD-FAST-9",
|
|
2118
|
+
ZstdFast10: "ZSTD-FAST-10",
|
|
2119
|
+
ZstdFast20: "ZSTD-FAST-20",
|
|
2120
|
+
ZstdFast30: "ZSTD-FAST-30",
|
|
2121
|
+
ZstdFast40: "ZSTD-FAST-40",
|
|
2122
|
+
ZstdFast50: "ZSTD-FAST-50",
|
|
2123
|
+
ZstdFast60: "ZSTD-FAST-60",
|
|
2124
|
+
ZstdFast70: "ZSTD-FAST-70",
|
|
2125
|
+
ZstdFast80: "ZSTD-FAST-80",
|
|
2126
|
+
ZstdFast90: "ZSTD-FAST-90",
|
|
2127
|
+
ZstdFast100: "ZSTD-FAST-100",
|
|
2128
|
+
ZstdFast500: "ZSTD-FAST-500",
|
|
2129
|
+
ZstdFast1000: "ZSTD-FAST-1000",
|
|
2130
|
+
Inherit: "INHERIT"
|
|
2131
|
+
};
|
|
2043
2132
|
var Service2 = {
|
|
2044
2133
|
Middleware: "MIDDLEWARE",
|
|
2045
2134
|
S3: "S3",
|
|
@@ -2047,6 +2136,14 @@ var Service2 = {
|
|
|
2047
2136
|
Sudo: "SUDO",
|
|
2048
2137
|
System: "SYSTEM"
|
|
2049
2138
|
};
|
|
2139
|
+
var SourceValueTypeInput = {
|
|
2140
|
+
None: "NONE",
|
|
2141
|
+
Default: "DEFAULT",
|
|
2142
|
+
Temporary: "TEMPORARY",
|
|
2143
|
+
Local: "LOCAL",
|
|
2144
|
+
Inherited: "INHERITED",
|
|
2145
|
+
Received: "RECEIVED"
|
|
2146
|
+
};
|
|
2050
2147
|
var VMWareMatchDatastoresWithDatasetsResultFilesystemTypeInput = {
|
|
2051
2148
|
Filesystem: "FILESYSTEM",
|
|
2052
2149
|
Volume: "VOLUME"
|
|
@@ -2060,8 +2157,8 @@ var SUPPORTED_API_VERSIONS = [
|
|
|
2060
2157
|
"v25.10.3",
|
|
2061
2158
|
"v25.10.4",
|
|
2062
2159
|
"v25.10.5",
|
|
2063
|
-
"
|
|
2064
|
-
"
|
|
2160
|
+
"v27.0.0",
|
|
2161
|
+
"v28.0.0"
|
|
2065
2162
|
];
|
|
2066
2163
|
|
|
2067
2164
|
// src/types/api-version.type.ts
|
|
@@ -2069,6 +2166,7 @@ var VersionCompatibility = /* @__PURE__ */ ((VersionCompatibility2) => {
|
|
|
2069
2166
|
VersionCompatibility2["Compatible"] = "compatible";
|
|
2070
2167
|
VersionCompatibility2["TooOld"] = "too-old";
|
|
2071
2168
|
VersionCompatibility2["TooNew"] = "too-new";
|
|
2169
|
+
VersionCompatibility2["Unsupported"] = "unsupported";
|
|
2072
2170
|
VersionCompatibility2["Invalid"] = "invalid";
|
|
2073
2171
|
return VersionCompatibility2;
|
|
2074
2172
|
})(VersionCompatibility || {});
|
|
@@ -2152,6 +2250,19 @@ function createJsonRpcMessage(method, params) {
|
|
|
2152
2250
|
};
|
|
2153
2251
|
}
|
|
2154
2252
|
|
|
2253
|
+
// src/utils/subscription.utils.ts
|
|
2254
|
+
function subscriptionName(event, params) {
|
|
2255
|
+
return params === void 0 ? event : `${event}:${JSON.stringify(params, sortKeys)}`;
|
|
2256
|
+
}
|
|
2257
|
+
function sortKeys(_key, value) {
|
|
2258
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
2259
|
+
return value;
|
|
2260
|
+
}
|
|
2261
|
+
return Object.fromEntries(
|
|
2262
|
+
Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)
|
|
2263
|
+
);
|
|
2264
|
+
}
|
|
2265
|
+
|
|
2155
2266
|
// src/api/truenas-api.ts
|
|
2156
2267
|
var EVENT_KINDS = ["added", "changed", "removed"];
|
|
2157
2268
|
var TrueNasApi = class {
|
|
@@ -2161,8 +2272,13 @@ var TrueNasApi = class {
|
|
|
2161
2272
|
/**
|
|
2162
2273
|
* Stream of job events from websocket.
|
|
2163
2274
|
* JSON-RPC 2.0 events have structure: { method: 'collection_update', params: { collection, fields, ... } }
|
|
2275
|
+
*
|
|
2276
|
+
* Reads `messages()` when first subscribed, not when this is built, as every
|
|
2277
|
+
* other verb does. A consumer that wraps the connection's `messages()` after
|
|
2278
|
+
* the client is created — a debug panel mixing in mocked answers — is
|
|
2279
|
+
* otherwise heard by `call` and `events` but not by job tracking.
|
|
2164
2280
|
*/
|
|
2165
|
-
this.jobEvents = this.connection.messages().pipe(
|
|
2281
|
+
this.jobEvents = defer(() => this.connection.messages()).pipe(
|
|
2166
2282
|
filter(
|
|
2167
2283
|
(res) => res.method === "collection_update" && res.params?.collection === "core.get_jobs"
|
|
2168
2284
|
),
|
|
@@ -2171,11 +2287,20 @@ var TrueNasApi = class {
|
|
|
2171
2287
|
share()
|
|
2172
2288
|
);
|
|
2173
2289
|
/**
|
|
2174
|
-
* One shared stream per
|
|
2175
|
-
*
|
|
2176
|
-
* for lives on the server and is per socket, not per caller.
|
|
2290
|
+
* One shared stream per subscription — see {@link events}. Keyed by the
|
|
2291
|
+
* name *with* its arguments: a tail of one file is not a tail of another.
|
|
2177
2292
|
*/
|
|
2178
2293
|
this.eventStreams = /* @__PURE__ */ new Map();
|
|
2294
|
+
/**
|
|
2295
|
+
* Releases awaiting their `core.unsubscribe` reply, per name. Middleware
|
|
2296
|
+
* sends a source's release notice (`terminate`) before that reply, and a
|
|
2297
|
+
* notice names only the collection; so an error-free one arriving inside
|
|
2298
|
+
* that window belongs to the release, not to a later subscription. One per
|
|
2299
|
+
* window, which closes on the reply whether or not a notice came.
|
|
2300
|
+
*/
|
|
2301
|
+
this.releasing = /* @__PURE__ */ new Map();
|
|
2302
|
+
/** The live subscription per name, told when the server ends it. */
|
|
2303
|
+
this.serverEnds = /* @__PURE__ */ new Map();
|
|
2179
2304
|
this.initializeJobEventsSubscription();
|
|
2180
2305
|
}
|
|
2181
2306
|
/**
|
|
@@ -2213,11 +2338,7 @@ var TrueNasApi = class {
|
|
|
2213
2338
|
withId(messageId),
|
|
2214
2339
|
map((msg) => {
|
|
2215
2340
|
if (msg.error) {
|
|
2216
|
-
|
|
2217
|
-
msg.error,
|
|
2218
|
-
"API call failed"
|
|
2219
|
-
);
|
|
2220
|
-
throw new Error(errorMessage);
|
|
2341
|
+
throw new TrueNasApiError(msg.error, method);
|
|
2221
2342
|
}
|
|
2222
2343
|
return msg.result;
|
|
2223
2344
|
}),
|
|
@@ -2270,21 +2391,32 @@ var TrueNasApi = class {
|
|
|
2270
2391
|
*
|
|
2271
2392
|
* The id comes from the first job event whose `message_ids` carries this
|
|
2272
2393
|
* request's id, not from the response: what a job method returns on the wire
|
|
2273
|
-
* differs by version (v25.10 answers with the id,
|
|
2394
|
+
* differs by version (v25.10 answers with the id, v27 with `null`), while
|
|
2274
2395
|
* the event correlation holds for both.
|
|
2275
2396
|
*
|
|
2276
|
-
* @returns
|
|
2397
|
+
* @returns the job's id; errors with a {@link TrueNasApiError} if the method is refused before a job starts
|
|
2277
2398
|
*/
|
|
2278
2399
|
callAndGetJobId(method, ...params) {
|
|
2279
2400
|
return defer(() => {
|
|
2280
2401
|
const message = createJsonRpcMessage(method, params[0]);
|
|
2281
|
-
const
|
|
2402
|
+
const started = this.jobEvents.pipe(
|
|
2282
2403
|
filter((job) => job.message_ids?.includes(message.id ?? "") ?? false),
|
|
2283
|
-
map((job) => job.id)
|
|
2284
|
-
|
|
2404
|
+
map((job) => job.id)
|
|
2405
|
+
);
|
|
2406
|
+
const refused = this.connection.messages().pipe(
|
|
2407
|
+
withId(message.id ?? ""),
|
|
2408
|
+
map((reply) => {
|
|
2409
|
+
if (reply.error) {
|
|
2410
|
+
throw new TrueNasApiError(reply.error, method);
|
|
2411
|
+
}
|
|
2412
|
+
}),
|
|
2413
|
+
ignoreElements()
|
|
2285
2414
|
);
|
|
2286
2415
|
const sending = this.connection.send(message);
|
|
2287
|
-
return
|
|
2416
|
+
return merge(started, refused).pipe(
|
|
2417
|
+
take(1),
|
|
2418
|
+
finalize(() => sending.unsubscribe())
|
|
2419
|
+
);
|
|
2288
2420
|
});
|
|
2289
2421
|
}
|
|
2290
2422
|
/**
|
|
@@ -2303,41 +2435,118 @@ var TrueNasApi = class {
|
|
|
2303
2435
|
switchMap((jobId) => this.trackJob(jobId))
|
|
2304
2436
|
);
|
|
2305
2437
|
}
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
* api.events('app.query').subscribe(event => {
|
|
2311
|
-
* if (event.msg === 'removed') return drop(event.id);
|
|
2312
|
-
* render(event.fields); // only reachable once narrowed
|
|
2313
|
-
* });
|
|
2314
|
-
* ```
|
|
2315
|
-
*
|
|
2316
|
-
* Narrow before reading `fields`: `removed` events almost never carry it.
|
|
2317
|
-
* Sources taking subscribe-time arguments are excluded; see {@link EventName}.
|
|
2318
|
-
*/
|
|
2319
|
-
events(event) {
|
|
2320
|
-
const existing = this.eventStreams.get(event);
|
|
2438
|
+
events(event, params) {
|
|
2439
|
+
const name = subscriptionName(event, params);
|
|
2440
|
+
this.listenForEndNotices();
|
|
2441
|
+
const existing = this.eventStreams.get(name);
|
|
2321
2442
|
if (existing) return existing;
|
|
2443
|
+
const forget = () => {
|
|
2444
|
+
if (this.eventStreams.get(name) === stream) this.eventStreams.delete(name);
|
|
2445
|
+
};
|
|
2322
2446
|
const stream = defer(() => {
|
|
2323
|
-
|
|
2324
|
-
|
|
2447
|
+
let subscriptionId;
|
|
2448
|
+
let released = false;
|
|
2449
|
+
let serverEnded = false;
|
|
2450
|
+
const ended = new Subject();
|
|
2451
|
+
this.serverEnds.set(name, ended);
|
|
2452
|
+
const subscribing = this.authenticated.pipe(distinctUntilChanged()).subscribe((isAuthenticated) => {
|
|
2453
|
+
subscriptionId = void 0;
|
|
2454
|
+
if (!isAuthenticated) return;
|
|
2455
|
+
const request = createJsonRpcMessage("core.subscribe", [name]);
|
|
2456
|
+
this.connection.messages().pipe(
|
|
2457
|
+
withId(request.id ?? ""),
|
|
2458
|
+
take(1),
|
|
2459
|
+
takeUntil(this.authenticated.pipe(filter((isUp) => !isUp)))
|
|
2460
|
+
).subscribe((reply) => {
|
|
2461
|
+
if (typeof reply.result !== "string") return;
|
|
2462
|
+
if (!released) subscriptionId = reply.result;
|
|
2463
|
+
else if (!serverEnded) this.unsubscribe(name, reply.result);
|
|
2464
|
+
});
|
|
2465
|
+
this.connection.send(request);
|
|
2325
2466
|
});
|
|
2326
|
-
|
|
2327
|
-
filter((res) => res.method === "collection_update"),
|
|
2328
|
-
map((res) => res.params),
|
|
2467
|
+
const updates = this.connection.messages().pipe(
|
|
2329
2468
|
filter(
|
|
2330
|
-
(
|
|
2469
|
+
(res) => res.method === "collection_update" && res.params?.collection === name
|
|
2331
2470
|
),
|
|
2471
|
+
map((res) => res.params),
|
|
2472
|
+
filter((params2) => EVENT_KINDS.includes(params2.msg)),
|
|
2473
|
+
// `merge` below ends only when both inputs do. The connection closing
|
|
2474
|
+
// for good ends this one; `ended` has to follow, or the stream never
|
|
2475
|
+
// completes and nothing tears down the resubscription.
|
|
2476
|
+
finalize(() => ended.complete())
|
|
2477
|
+
);
|
|
2478
|
+
const end = ended.pipe(
|
|
2479
|
+
map((notice) => {
|
|
2480
|
+
subscriptionId = void 0;
|
|
2481
|
+
serverEnded = true;
|
|
2482
|
+
if (notice.error) {
|
|
2483
|
+
throw new TrueNasApiError(
|
|
2484
|
+
{ code: -32001, message: notice.error.reason, data: notice.error },
|
|
2485
|
+
name
|
|
2486
|
+
);
|
|
2487
|
+
}
|
|
2488
|
+
})
|
|
2489
|
+
);
|
|
2490
|
+
return merge(updates, end).pipe(
|
|
2491
|
+
takeWhile((change) => change !== void 0),
|
|
2332
2492
|
// `collection` is the subscription the caller already named; what is
|
|
2333
2493
|
// left is exactly the payload the directory declares, plus its tag.
|
|
2334
2494
|
map(({ collection, ...change }) => change),
|
|
2335
|
-
|
|
2495
|
+
// An ended stream stays ended (below), so the next `events()` call for
|
|
2496
|
+
// this name needs a new one.
|
|
2497
|
+
tap({ complete: forget, error: forget }),
|
|
2498
|
+
finalize(() => {
|
|
2499
|
+
released = true;
|
|
2500
|
+
subscribing.unsubscribe();
|
|
2501
|
+
if (this.serverEnds.get(name) === ended) this.serverEnds.delete(name);
|
|
2502
|
+
if (subscriptionId) this.unsubscribe(name, subscriptionId);
|
|
2503
|
+
})
|
|
2336
2504
|
);
|
|
2337
|
-
}).pipe(
|
|
2338
|
-
|
|
2505
|
+
}).pipe(
|
|
2506
|
+
share({
|
|
2507
|
+
resetOnRefCountZero: true,
|
|
2508
|
+
resetOnComplete: false,
|
|
2509
|
+
resetOnError: false
|
|
2510
|
+
})
|
|
2511
|
+
);
|
|
2512
|
+
this.eventStreams.set(name, stream);
|
|
2339
2513
|
return stream;
|
|
2340
2514
|
}
|
|
2515
|
+
/**
|
|
2516
|
+
* Release a subscription by the id `core.subscribe` answered with, holding
|
|
2517
|
+
* its notice window (see {@link releasing}) open until the reply.
|
|
2518
|
+
*/
|
|
2519
|
+
unsubscribe(name, subscriptionId) {
|
|
2520
|
+
const release = { noticed: false };
|
|
2521
|
+
this.releasing.set(name, [...this.releasing.get(name) ?? [], release]);
|
|
2522
|
+
const request = createJsonRpcMessage("core.unsubscribe", [subscriptionId]);
|
|
2523
|
+
this.connection.messages().pipe(
|
|
2524
|
+
withId(request.id ?? ""),
|
|
2525
|
+
take(1),
|
|
2526
|
+
takeUntil(this.authenticated.pipe(filter((isUp) => !isUp))),
|
|
2527
|
+
finalize(() => {
|
|
2528
|
+
const open = (this.releasing.get(name) ?? []).filter((r) => r !== release);
|
|
2529
|
+
if (open.length) this.releasing.set(name, open);
|
|
2530
|
+
else this.releasing.delete(name);
|
|
2531
|
+
})
|
|
2532
|
+
).subscribe();
|
|
2533
|
+
this.connection.send(request);
|
|
2534
|
+
}
|
|
2535
|
+
/**
|
|
2536
|
+
* Route each `notify_unsubscribed` once: an error-free one inside an open
|
|
2537
|
+
* release window is that release's, anything else ends the name's live
|
|
2538
|
+
* subscription. Installed on the first `events()` call, before any stream
|
|
2539
|
+
* listens.
|
|
2540
|
+
*/
|
|
2541
|
+
listenForEndNotices() {
|
|
2542
|
+
if (this.endNotices) return;
|
|
2543
|
+
this.endNotices = this.connection.messages().pipe(filter((res) => res.method === "notify_unsubscribed")).subscribe((res) => {
|
|
2544
|
+
const notice = res.params;
|
|
2545
|
+
const release = notice.error ? void 0 : this.releasing.get(notice.collection)?.find((r) => !r.noticed);
|
|
2546
|
+
if (release) release.noticed = true;
|
|
2547
|
+
else this.serverEnds.get(notice.collection)?.next(notice);
|
|
2548
|
+
});
|
|
2549
|
+
}
|
|
2341
2550
|
/**
|
|
2342
2551
|
* Convenience wrapper for auth.generate_token API call.
|
|
2343
2552
|
*
|
|
@@ -2370,12 +2579,10 @@ var TrueNasApi = class {
|
|
|
2370
2579
|
filter((job) => job.id === jobId),
|
|
2371
2580
|
map((job) => job)
|
|
2372
2581
|
);
|
|
2373
|
-
const
|
|
2374
|
-
|
|
2375
|
-
]).pipe(
|
|
2376
|
-
map((jobs) => jobs[0]),
|
|
2377
|
-
share()
|
|
2582
|
+
const readJob = () => this.dispatch("core.get_jobs", [[["id", "=", jobId]]]).pipe(
|
|
2583
|
+
map((jobs) => jobs[0])
|
|
2378
2584
|
);
|
|
2585
|
+
const read$ = readJob().pipe(share());
|
|
2379
2586
|
const missing$ = read$.pipe(
|
|
2380
2587
|
filter((job) => job === void 0),
|
|
2381
2588
|
takeUntil(updates$)
|
|
@@ -2384,10 +2591,29 @@ var TrueNasApi = class {
|
|
|
2384
2591
|
filter((job) => job !== void 0),
|
|
2385
2592
|
takeUntil(updates$)
|
|
2386
2593
|
);
|
|
2387
|
-
|
|
2594
|
+
const reread$ = this.authenticated.pipe(
|
|
2595
|
+
distinctUntilChanged(),
|
|
2596
|
+
skipWhile((isUp) => !isUp),
|
|
2597
|
+
pairwise(),
|
|
2598
|
+
filter(([wasUp, isUp]) => !wasUp && isUp),
|
|
2599
|
+
switchMap(
|
|
2600
|
+
() => readJob().pipe(
|
|
2601
|
+
// Stale once events flow again, as the opening read is; they carry the end.
|
|
2602
|
+
takeUntil(updates$),
|
|
2603
|
+
// A failed read waits for the next reconnect.
|
|
2604
|
+
catchError(() => EMPTY)
|
|
2605
|
+
)
|
|
2606
|
+
),
|
|
2607
|
+
share()
|
|
2608
|
+
);
|
|
2609
|
+
const gone$ = reread$.pipe(filter((job) => job === void 0));
|
|
2610
|
+
const rereadJob$ = reread$.pipe(
|
|
2611
|
+
filter((job) => job !== void 0)
|
|
2612
|
+
);
|
|
2613
|
+
return merge(snapshot$, updates$, rereadJob$).pipe(
|
|
2388
2614
|
takeWhile((job) => !isJobFinished(job), true),
|
|
2389
2615
|
// Include the final state
|
|
2390
|
-
takeUntil(missing$)
|
|
2616
|
+
takeUntil(merge(missing$, gone$))
|
|
2391
2617
|
);
|
|
2392
2618
|
}
|
|
2393
2619
|
/**
|
|
@@ -2427,7 +2653,7 @@ var apiVersionConfig = {
|
|
|
2427
2653
|
* not write its client, so the ceiling is the newest version in
|
|
2428
2654
|
* `CLIENT_BY_VERSION_KEY`. A factory test asserts the two agree.
|
|
2429
2655
|
*/
|
|
2430
|
-
MAX_SUPPORTED_VERSION: "
|
|
2656
|
+
MAX_SUPPORTED_VERSION: "v28.0.0",
|
|
2431
2657
|
/**
|
|
2432
2658
|
* Version assumed when discovery is CORS-blocked, as v25.10.0's
|
|
2433
2659
|
* `/api/versions` is. Used only for an appliance that answers a reachability
|
|
@@ -2487,6 +2713,9 @@ function checkVersionCompatibility(version) {
|
|
|
2487
2713
|
if (compareVersions(version, maxVersion) > 0) {
|
|
2488
2714
|
return "too-new" /* TooNew */;
|
|
2489
2715
|
}
|
|
2716
|
+
if (!SUPPORTED_API_VERSIONS.includes(version.version)) {
|
|
2717
|
+
return "unsupported" /* Unsupported */;
|
|
2718
|
+
}
|
|
2490
2719
|
return "compatible" /* Compatible */;
|
|
2491
2720
|
}
|
|
2492
2721
|
function isVersionSupported(version) {
|
|
@@ -2592,7 +2821,7 @@ var TrueNasAuthenticator = class _TrueNasAuthenticator {
|
|
|
2592
2821
|
/**
|
|
2593
2822
|
* `login_options` asking for a reconnect token, when the server understands it.
|
|
2594
2823
|
*
|
|
2595
|
-
* Omitted below
|
|
2824
|
+
* Omitted below v27. `AuthCommonOptions` is `additionalProperties: false`
|
|
2596
2825
|
* there and has only `user_info`, so sending the member is a validation
|
|
2597
2826
|
* error, not an ignored field — it would fail login outright on the oldest
|
|
2598
2827
|
* version this client supports.
|
|
@@ -2600,7 +2829,7 @@ var TrueNasAuthenticator = class _TrueNasAuthenticator {
|
|
|
2600
2829
|
/**
|
|
2601
2830
|
* Two things this does not do, both deliberate and both worth knowing.
|
|
2602
2831
|
*
|
|
2603
|
-
* There is no way for a consumer to decline: every
|
|
2832
|
+
* There is no way for a consumer to decline: every v27+ password login now
|
|
2604
2833
|
* mints a single-use credential carrying that session's roles, whether or not
|
|
2605
2834
|
* the caller wants one. And the auto-relogin in the constructor subscribes
|
|
2606
2835
|
* with no observer, so the token it mints is dropped — a caller reconnecting
|
|
@@ -2735,7 +2964,7 @@ var TrueNasAuthenticator = class _TrueNasAuthenticator {
|
|
|
2735
2964
|
* e.g. to open another connection without asking for the password again.
|
|
2736
2965
|
*
|
|
2737
2966
|
* Tokens are single-use, short-lived, and voided by a `middlewared` restart.
|
|
2738
|
-
* On
|
|
2967
|
+
* On v27+ each successful login mints a new one; store the newest. Token
|
|
2739
2968
|
* sessions are not re-logged automatically on reconnect — the caller must.
|
|
2740
2969
|
*/
|
|
2741
2970
|
loginWithToken(token) {
|
|
@@ -2794,7 +3023,7 @@ var TrueNasAuthenticator = class _TrueNasAuthenticator {
|
|
|
2794
3023
|
);
|
|
2795
3024
|
}
|
|
2796
3025
|
/**
|
|
2797
|
-
* No reconnect token is requested here, though
|
|
3026
|
+
* No reconnect token is requested here, though v27+ would mint one: an
|
|
2798
3027
|
* api-key session already reconnects without a prompt, since the key is held
|
|
2799
3028
|
* and replayed. The token exists for the credential that cannot be.
|
|
2800
3029
|
*/
|
|
@@ -3457,7 +3686,7 @@ function toAppState(state) {
|
|
|
3457
3686
|
case "STARTING":
|
|
3458
3687
|
case "DEPLOYING":
|
|
3459
3688
|
return "DEPLOYING" /* Deploying */;
|
|
3460
|
-
// v25.10's freezer states are the same idea as
|
|
3689
|
+
// v25.10's freezer states are the same idea as v27's `SUSPENDED`: the
|
|
3461
3690
|
// processes are paused, not exited. `FROZEN` is that pause completed, so it
|
|
3462
3691
|
// and `SUSPENDED` are the same answer.
|
|
3463
3692
|
case "SUSPENDED":
|
|
@@ -3569,14 +3798,14 @@ function toContainer(instance) {
|
|
|
3569
3798
|
// declares only `description`, but the API returns `architecture`, `os`,
|
|
3570
3799
|
// `release` and more, and callers were already receiving them. Narrowing
|
|
3571
3800
|
// to the declared field would take data away to match a type that was
|
|
3572
|
-
// always an under-declaration — the same call as the
|
|
3801
|
+
// always an under-declaration — the same call as the v27 `description`
|
|
3573
3802
|
// widening, decided the same way.
|
|
3574
3803
|
image: instance.image.description === null ? void 0 : { ...instance.image, description: instance.image.description }
|
|
3575
3804
|
};
|
|
3576
3805
|
}
|
|
3577
|
-
var
|
|
3806
|
+
var TrueNasApiClientV27 = class extends TrueNasApiClient {
|
|
3578
3807
|
/**
|
|
3579
|
-
* Create
|
|
3808
|
+
* Create v27-specific operation mappings
|
|
3580
3809
|
*
|
|
3581
3810
|
* Operations return Observable<Job | null>:
|
|
3582
3811
|
* - Async operations emit Job updates until complete
|
|
@@ -3590,7 +3819,7 @@ var TrueNasApiClientV26 = class extends TrueNasApiClient {
|
|
|
3590
3819
|
containerQuery: () => this.api.query("container.query").pipe(
|
|
3591
3820
|
map((containers) => containers.map(toContainer2))
|
|
3592
3821
|
),
|
|
3593
|
-
// container.start is synchronous in
|
|
3822
|
+
// container.start is synchronous in v27.0.0 - emit null
|
|
3594
3823
|
containerStart: (id) => this.api.call("container.start", [parseInt(id, 10)]).pipe(map(() => null)),
|
|
3595
3824
|
// container.stop emits job updates
|
|
3596
3825
|
containerStop: (id, options) => this.api.job("container.stop", [
|
|
@@ -3600,7 +3829,7 @@ var TrueNasApiClientV26 = class extends TrueNasApiClient {
|
|
|
3600
3829
|
force_after_timeout: options.force
|
|
3601
3830
|
}
|
|
3602
3831
|
]),
|
|
3603
|
-
//
|
|
3832
|
+
// v27.0.0 doesn't have container.restart - chain stop + start
|
|
3604
3833
|
// Emits Job updates during stop, then null when start completes
|
|
3605
3834
|
containerRestart: (id, options) => {
|
|
3606
3835
|
const numericId = parseInt(id, 10);
|
|
@@ -3652,9 +3881,9 @@ function toContainer2(container) {
|
|
|
3652
3881
|
description
|
|
3653
3882
|
};
|
|
3654
3883
|
}
|
|
3655
|
-
var
|
|
3884
|
+
var TrueNasApiClientV28 = class extends TrueNasApiClient {
|
|
3656
3885
|
/**
|
|
3657
|
-
* Create
|
|
3886
|
+
* Create v28-specific operation mappings
|
|
3658
3887
|
*
|
|
3659
3888
|
* Operations return Observable<Job | null>:
|
|
3660
3889
|
* - Async operations emit Job updates until complete
|
|
@@ -3668,7 +3897,7 @@ var TrueNasApiClientV27 = class extends TrueNasApiClient {
|
|
|
3668
3897
|
containerQuery: () => this.api.query("container.query").pipe(
|
|
3669
3898
|
map((containers) => containers.map(toContainer3))
|
|
3670
3899
|
),
|
|
3671
|
-
// container.start is synchronous in
|
|
3900
|
+
// container.start is synchronous in v28 - emit null
|
|
3672
3901
|
containerStart: (id) => this.api.call("container.start", [parseInt(id, 10)]).pipe(map(() => null)),
|
|
3673
3902
|
// container.stop emits job updates
|
|
3674
3903
|
containerStop: (id, options) => this.api.job("container.stop", [
|
|
@@ -3678,7 +3907,7 @@ var TrueNasApiClientV27 = class extends TrueNasApiClient {
|
|
|
3678
3907
|
force_after_timeout: options.force
|
|
3679
3908
|
}
|
|
3680
3909
|
]),
|
|
3681
|
-
//
|
|
3910
|
+
// v28 still has no container.restart - chain stop + start.
|
|
3682
3911
|
// Emits Job updates during stop, then null when start completes.
|
|
3683
3912
|
containerRestart: (id, options) => {
|
|
3684
3913
|
const numericId = parseInt(id, 10);
|
|
@@ -3700,7 +3929,7 @@ var TrueNasApiClientV27 = class extends TrueNasApiClient {
|
|
|
3700
3929
|
)
|
|
3701
3930
|
);
|
|
3702
3931
|
},
|
|
3703
|
-
// Absent options are omitted, as in
|
|
3932
|
+
// Absent options are omitted, as in v27: `[id, null]` fails validation.
|
|
3704
3933
|
containerDelete: (id, options) => this.api.job(
|
|
3705
3934
|
"container.delete",
|
|
3706
3935
|
options ? [parseInt(id, 10), options] : [parseInt(id, 10)]
|
|
@@ -3797,13 +4026,16 @@ var NoCompatibleVersionsError = class extends VersionDiscoveryError {
|
|
|
3797
4026
|
constructor(hostname, availableVersions, supportedRange = {
|
|
3798
4027
|
min: apiVersionConfig.MIN_SUPPORTED_VERSION,
|
|
3799
4028
|
max: apiVersionConfig.MAX_SUPPORTED_VERSION
|
|
3800
|
-
}) {
|
|
4029
|
+
}, supportedVersions = SUPPORTED_API_VERSIONS) {
|
|
3801
4030
|
super(
|
|
3802
|
-
`No compatible API versions available. Supported: ${
|
|
4031
|
+
`No compatible API versions available. Supported: ${supportedVersions.join(
|
|
4032
|
+
", "
|
|
4033
|
+
)}. Available: ${availableVersions.join(", ")}`,
|
|
3803
4034
|
hostname
|
|
3804
4035
|
);
|
|
3805
4036
|
this.availableVersions = availableVersions;
|
|
3806
4037
|
this.supportedRange = supportedRange;
|
|
4038
|
+
this.supportedVersions = supportedVersions;
|
|
3807
4039
|
}
|
|
3808
4040
|
};
|
|
3809
4041
|
var InvalidVersionResponseError = class extends VersionDiscoveryError {
|
|
@@ -4222,8 +4454,8 @@ function selectRepresentativeFailure(failures) {
|
|
|
4222
4454
|
}
|
|
4223
4455
|
var CLIENT_BY_VERSION_KEY = {
|
|
4224
4456
|
"25.10": TrueNasApiClientV2510,
|
|
4225
|
-
"
|
|
4226
|
-
"
|
|
4457
|
+
"27": TrueNasApiClientV27,
|
|
4458
|
+
"28": TrueNasApiClientV28
|
|
4227
4459
|
};
|
|
4228
4460
|
function clientVersionKey(version) {
|
|
4229
4461
|
if (version.year <= legacyCutoffYear) {
|
|
@@ -4280,6 +4512,6 @@ var UserRole = /* @__PURE__ */ ((UserRole2) => {
|
|
|
4280
4512
|
return UserRole2;
|
|
4281
4513
|
})(UserRole || {});
|
|
4282
4514
|
|
|
4283
|
-
export { AppState, AuthError, AuthErrorCode, AuthResponseType, InvalidVersionResponseError, JobState, NoCompatibleVersionsError, SUPPORTED_API_VERSIONS, TrueNasApiClient, TrueNasApiClientV2510,
|
|
4284
|
-
//# sourceMappingURL=chunk-
|
|
4285
|
-
//# sourceMappingURL=chunk-
|
|
4515
|
+
export { AppState, AuthError, AuthErrorCode, AuthResponseType, InvalidVersionResponseError, JobState, NoCompatibleVersionsError, SUPPORTED_API_VERSIONS, TrueNasApiClient, TrueNasApiClientV2510, TrueNasApiClientV27, TrueNasApiClientV28, TrueNasApiError, TrueNasAuthMechanism, TrueNasAuthenticator, TrueNasConnection, UserRole, VersionCompatibility, VersionDiscovery, VersionDiscoveryError, VersionDiscoveryNetworkError, VersionDiscoveryTimeoutError, VersionEndpointNotFoundError, VersionTooNewError, VersionTooOldError, clientClassFor, consoleLogger, createTrueNasClient, getApiErrorMessage, getCloseMessage, isJobFinished, noopLogger, parseApiVersion, policyViolationCloseCode, subscriptionName, v25_10_0_exports, v25_10_1_exports, v25_10_2_exports, v25_10_3_exports, v25_10_4_exports, v25_10_5_exports, v27_0_0_exports, v28_0_0_exports };
|
|
4516
|
+
//# sourceMappingURL=chunk-K7BL6J3Z.js.map
|
|
4517
|
+
//# sourceMappingURL=chunk-K7BL6J3Z.js.map
|