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