@saasicat/ui-vue 0.24.2 → 0.26.0

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.
Files changed (90) hide show
  1. package/README.md +32 -8
  2. package/dist/{catalog-DJcfm5N2.d.cts → catalog-Dch1Ryw0.d.cts} +353 -69
  3. package/dist/{catalog-DJcfm5N2.d.ts → catalog-Dch1Ryw0.d.ts} +353 -69
  4. package/dist/{chunk-F7NRM6KI.js → chunk-BPF2BMCQ.js} +748 -24
  5. package/dist/{chunk-D74FFCCP.js → chunk-NEXTZZRQ.js} +95 -177
  6. package/dist/{chunk-TQQMKWN6.js → chunk-O3J3ITF2.js} +119 -7
  7. package/dist/client/index.cjs +758 -83
  8. package/dist/client/index.d.cts +414 -9
  9. package/dist/client/index.d.ts +414 -9
  10. package/dist/client/index.js +84 -12
  11. package/dist/index.cjs +1325 -294
  12. package/dist/index.d.cts +205 -15
  13. package/dist/index.d.ts +205 -15
  14. package/dist/index.js +566 -240
  15. package/dist/quasar/index.cjs +574 -62
  16. package/dist/quasar/index.d.cts +61 -6
  17. package/dist/quasar/index.d.ts +61 -6
  18. package/dist/quasar/index.js +83 -19
  19. package/dist/{use-sa-theme-OUBlaqgl.d.ts → resource-registry-D7Xjs-5f.d.cts} +157 -7
  20. package/dist/{use-sa-theme-B5t7oXph.d.cts → resource-registry-TaR7rtq0.d.ts} +157 -7
  21. package/package.json +4 -3
  22. package/src/client/admin-error.ts +552 -0
  23. package/src/client/admin-resource-client.ts +9 -12
  24. package/src/client/batch-column-fetcher.ts +2 -2
  25. package/src/client/boot-loader.ts +12 -1
  26. package/src/client/http/axios-http-client.ts +383 -0
  27. package/src/client/http/fetch-http-client.ts +89 -0
  28. package/src/client/http/index.ts +6 -0
  29. package/src/client/http-json.ts +82 -16
  30. package/src/client/i18n/messages/discovery.ts +0 -6
  31. package/src/client/i18n/messages/errors.ts +44 -0
  32. package/src/client/i18n/messages/marketing.ts +0 -6
  33. package/src/client/i18n/messages/promos.ts +0 -2
  34. package/src/client/i18n/messages/shell.ts +12 -0
  35. package/src/client/i18n/messages.ts +3 -0
  36. package/src/client/index.ts +6 -0
  37. package/src/client/manifest-loader.ts +65 -15
  38. package/src/client/resources/audit.resource.ts +25 -0
  39. package/src/client/resources/define-resource.ts +166 -0
  40. package/src/client/resources/index.ts +42 -0
  41. package/src/client/resources/list-resource.ts +176 -0
  42. package/src/client/resources/plans.resource.ts +169 -0
  43. package/src/client/resources/resource-request.ts +117 -0
  44. package/src/client/resources/tenants.resource.ts +27 -0
  45. package/src/client/types.ts +10 -3
  46. package/src/components/BundleVersionPublishDialog.vue +1 -1
  47. package/src/components/MarketingPromotionsTab.vue +263 -241
  48. package/src/components/ThemeSwitcher.vue +85 -0
  49. package/src/components/admin-page/AdminAccordion.vue +125 -0
  50. package/src/components/bundle-editor/BundleCreatePanel.vue +1 -1
  51. package/src/components/dialogs/PromoCodeDialogFields.vue +20 -20
  52. package/src/components/plan/PlanCycleToggle.vue +1 -1
  53. package/src/components/plan-detail/PlanVersionsPanel.vue +5 -2
  54. package/src/index.ts +5 -0
  55. package/src/pages-standard/AdminLayout.vue +57 -0
  56. package/src/pages-standard/AdminManifestErrorPage.vue +4 -3
  57. package/src/pages-standard/BundlesPage.vue +5 -35
  58. package/src/pages-standard/DiscoveryPage.vue +8 -1
  59. package/src/pages-standard/MarketingCatalogPage.vue +14 -3
  60. package/src/pages-standard/SuperAdminLoginPage.vue +23 -2
  61. package/src/pages-standard/SuperAdminSetupWizard.vue +37 -3
  62. package/src/pages-standard/bundles-page/BundleAccordionList.vue +30 -32
  63. package/src/pages-standard/discovery-page/DiscoveryFeatureCard.vue +80 -88
  64. package/src/pages-standard/discovery-page/DiscoveryQuotaCard.vue +73 -79
  65. package/src/pages-standard/marketing-catalog/MarketingCatalogAdmin.vue +44 -6
  66. package/src/pages-standard/marketing-catalog/MarketingCatalogPreview.vue +8 -2
  67. package/src/pages-tenant/PackageSnapshotPanel.vue +31 -2
  68. package/src/quasar/confirm.ts +83 -0
  69. package/src/quasar/create-super-admin-app.ts +84 -10
  70. package/src/quasar/dark-bridge.ts +47 -10
  71. package/src/quasar/index.ts +1 -0
  72. package/src/ui/theme/components/accordion.css +118 -0
  73. package/src/ui/theme/index.css +1 -0
  74. package/src/vue/create-admin-routes.ts +9 -1
  75. package/src/vue/platform-loaders.ts +12 -2
  76. package/src/vue/resource-registry.ts +211 -0
  77. package/src/vue/super-admin-context.ts +20 -5
  78. package/src/vue/ui-confirm.ts +76 -0
  79. package/src/vue/use-api-list.ts +50 -45
  80. package/src/vue/use-async-action.ts +170 -0
  81. package/src/vue/use-async-data.ts +83 -0
  82. package/src/vue/use-bundles.ts +34 -7
  83. package/src/vue/use-catalog-entries.ts +37 -12
  84. package/src/vue/use-discovery.ts +27 -6
  85. package/src/vue/use-marketing-projections.ts +29 -7
  86. package/src/vue/use-plans.ts +36 -8
  87. package/src/vue/use-promotions.ts +27 -7
  88. package/src/vue/use-resource-list.ts +272 -0
  89. package/src/vue/use-sa-theme.ts +34 -5
  90. package/src/vue/use-tenant-subscription-bundles.ts +22 -3
package/dist/index.js CHANGED
@@ -4,23 +4,18 @@ import {
4
4
  ActionRegistry,
5
5
  BatchColumnDriftError,
6
6
  BatchColumnFetcher,
7
- BootLoadError,
8
- BootLoader,
9
7
  DEFAULT_STANDARD_PAGE_ROUTES,
10
- HttpJsonError,
11
8
  IDENTITY_ACCENTS,
12
9
  IDENTITY_ACCENT_VALUES,
13
10
  IDENTITY_NEUTRAL,
14
11
  IDENTITY_NEUTRAL_VALUE,
15
- ManifestLoadError,
16
- ManifestLoader,
17
12
  MissingHandlerError,
18
13
  buildRoutes,
19
14
  buildSidebar,
20
15
  countPlans,
21
16
  createAdminResourceClient,
17
+ createAxiosHttpClient,
22
18
  defaultSectionOrder,
23
- getJson,
24
19
  identityAccentAt,
25
20
  identityAccentFor,
26
21
  identityChipStyle,
@@ -28,18 +23,19 @@ import {
28
23
  isExpired,
29
24
  isFutureScheduled,
30
25
  isProductionBoot,
31
- postJson,
32
26
  resolveExtension,
33
27
  resolveLoginBranding,
34
28
  resolvePlans,
35
- todayIsoDate,
36
- trimTrailingSlashes
37
- } from "./chunk-D74FFCCP.js";
29
+ todayIsoDate
30
+ } from "./chunk-NEXTZZRQ.js";
38
31
  import {
32
+ SA_COLOR_SCHEMES,
39
33
  SA_LOCALE_STORAGE_KEY,
40
34
  SA_THEME_KEY,
35
+ SA_THEME_STORAGE_KEY,
41
36
  SUPER_ADMIN_ACTIONS_KEY,
42
37
  SUPER_ADMIN_BRAND_KEY,
38
+ SUPER_ADMIN_CONFIRM_KEY,
43
39
  SUPER_ADMIN_ENDPOINTS_KEY,
44
40
  SUPER_ADMIN_EXTENSIONS_KEY,
45
41
  SUPER_ADMIN_HTTP_KEY,
@@ -48,29 +44,77 @@ import {
48
44
  SUPER_ADMIN_MANIFEST_CLEAR_CACHE_KEY,
49
45
  SUPER_ADMIN_MANIFEST_KEY,
50
46
  SUPER_ADMIN_NOTIFY_KEY,
47
+ SUPER_ADMIN_RESOURCES_KEY,
51
48
  buildNavigationGuard,
49
+ createPlatformLoaders,
50
+ createResourceRegistry,
52
51
  createSaTheme,
53
52
  createSuperAdminI18n,
53
+ resolveSuperAdminEndpoints,
54
+ useResource,
54
55
  useSaMessages,
55
56
  useSaTheme,
56
57
  useSuperAdminI18n
57
- } from "./chunk-TQQMKWN6.js";
58
+ } from "./chunk-O3J3ITF2.js";
58
59
  import {
60
+ AdminError,
61
+ BootLoadError,
62
+ BootLoader,
59
63
  DEFAULT_SA_LOCALE,
64
+ HttpJsonError,
65
+ LIST_FIRST_PAGE,
66
+ LIST_PAGE_SIZE_DEFAULT,
67
+ LIST_PAGE_SIZE_MAX,
68
+ LIST_PAGINATION_PARAMS,
69
+ ManifestLoadError,
70
+ ManifestLoader,
60
71
  SA_INTL_LOCALES,
61
72
  SA_LOCALES,
62
73
  SA_LOCALE_LABELS,
63
74
  SA_MESSAGES,
75
+ adminErrorMessage,
76
+ auditResource,
77
+ bindResource,
78
+ clampListPage,
79
+ clampListPageSize,
80
+ createFetchHttpClient,
64
81
  createSaCatalog,
65
82
  defaultHttpClient,
66
83
  defaultKvStore,
84
+ defineListOp,
67
85
  defineMessages,
86
+ defineResource,
87
+ filterQueryString,
68
88
  formatCurrency,
69
89
  formatMessage,
90
+ getJson,
91
+ isAdminError,
92
+ isAxiosNoResponseError,
93
+ isEmptyResponse,
94
+ isPlatformError,
70
95
  isSaBuiltinLocale,
96
+ isSentInQuery,
97
+ isTransportFailure,
98
+ listUrl,
99
+ markEmptyResponse,
100
+ markPlatformError,
101
+ markTransportFailure,
71
102
  mergeMessages,
72
- resolveMessages
73
- } from "./chunk-F7NRM6KI.js";
103
+ planVersionsResource,
104
+ plansResource,
105
+ platformResources,
106
+ postJson,
107
+ readErrorCode,
108
+ readErrorDetail,
109
+ readListPage,
110
+ requestJson,
111
+ requestJsonBody,
112
+ requireServerAnswer,
113
+ resolveMessages,
114
+ tenantsResource,
115
+ toAdminError,
116
+ trimTrailingSlashes
117
+ } from "./chunk-BPF2BMCQ.js";
74
118
 
75
119
  // src/vue/use-super-admin-context.ts
76
120
  import { inject } from "vue";
@@ -211,22 +255,17 @@ import { ref, watch } from "vue";
211
255
  function useApiList(options) {
212
256
  const http = options.http ?? defaultHttpClient();
213
257
  const items = ref([]);
214
- const page = ref(1);
215
- const pageSize = ref(50);
258
+ const page = ref(LIST_FIRST_PAGE);
259
+ const pageSize = ref(LIST_PAGE_SIZE_DEFAULT);
216
260
  const total = ref(0);
217
261
  const loading = ref(false);
218
262
  const error = ref(null);
219
263
  function buildUrl() {
220
- const params = new URLSearchParams();
221
- params.set("page", String(page.value));
222
- params.set("pageSize", String(pageSize.value));
223
- const f = options.filter?.value ?? {};
224
- for (const [k, v] of Object.entries(f)) {
225
- if (v === void 0 || v === null || v === "") continue;
226
- params.set(k, String(v));
227
- }
228
- const sep = options.endpoint.includes("?") ? "&" : "?";
229
- return `${options.endpoint}${sep}${params.toString()}`;
264
+ return listUrl(options.endpoint, {
265
+ page: page.value,
266
+ pageSize: pageSize.value,
267
+ filter: options.filter?.value
268
+ });
230
269
  }
231
270
  async function load() {
232
271
  loading.value = true;
@@ -239,20 +278,11 @@ function useApiList(options) {
239
278
  if (res.status !== 200) {
240
279
  throw new Error(`Endpoint ${options.endpoint} \u2192 HTTP ${res.status}`);
241
280
  }
242
- const raw = await res.json();
243
- if (Array.isArray(raw)) {
244
- items.value = raw;
245
- total.value = raw.length;
246
- } else if (raw !== null && typeof raw === "object") {
247
- const body = raw;
248
- items.value = body.items ?? [];
249
- if (typeof body.page === "number") page.value = body.page;
250
- if (typeof body.pageSize === "number") pageSize.value = body.pageSize;
251
- if (typeof body.total === "number") total.value = body.total;
252
- } else {
253
- items.value = [];
254
- total.value = 0;
255
- }
281
+ const body = readListPage(await res.json());
282
+ items.value = body.items;
283
+ if (typeof body.page === "number") page.value = body.page;
284
+ if (typeof body.pageSize === "number") pageSize.value = body.pageSize;
285
+ if (typeof body.total === "number") total.value = body.total;
256
286
  } catch (err) {
257
287
  error.value = err instanceof Error ? err : new Error(String(err));
258
288
  items.value = [];
@@ -262,19 +292,19 @@ function useApiList(options) {
262
292
  }
263
293
  }
264
294
  async function goToPage(p) {
265
- page.value = Math.max(1, Math.floor(p));
295
+ page.value = clampListPage(p);
266
296
  await load();
267
297
  }
268
298
  async function setPageSize(size) {
269
- pageSize.value = Math.min(200, Math.max(1, Math.floor(size)));
270
- page.value = 1;
299
+ pageSize.value = clampListPageSize(size);
300
+ page.value = LIST_FIRST_PAGE;
271
301
  await load();
272
302
  }
273
303
  if (options.filter) {
274
304
  watch(
275
305
  options.filter,
276
306
  () => {
277
- page.value = 1;
307
+ page.value = LIST_FIRST_PAGE;
278
308
  void load();
279
309
  },
280
310
  { deep: true }
@@ -320,15 +350,189 @@ function usePagination(rows, initialRowsPerPage = DEFAULT_ROWS_PER_PAGE) {
320
350
  return { page, rowsPerPage, total, pagedRows };
321
351
  }
322
352
 
353
+ // src/vue/use-async-action.ts
354
+ import { inject as inject3, ref as ref3 } from "vue";
355
+ function report(announce) {
356
+ try {
357
+ announce();
358
+ } catch (err) {
359
+ queueMicrotask(() => {
360
+ throw err;
361
+ });
362
+ }
363
+ }
364
+ function useAsyncAction(fn, options = {}) {
365
+ const pending = ref3(false);
366
+ const error = ref3(null);
367
+ const notify = options.notify ?? inject3(SUPER_ADMIN_NOTIFY_KEY, void 0);
368
+ const messages = useSaMessages("errors");
369
+ const notifyOn = options.notifyOn ?? "error";
370
+ let inFlight = 0;
371
+ let generation = 0;
372
+ async function run(...args) {
373
+ const mine = ++generation;
374
+ inFlight++;
375
+ pending.value = true;
376
+ error.value = null;
377
+ try {
378
+ const result = await fn(...args);
379
+ await options.onSuccess?.(result);
380
+ if (notifyOn === "both") {
381
+ report(() => {
382
+ const message = typeof options.successMessage === "function" ? options.successMessage() : options.successMessage;
383
+ if (message) notify?.("positive", message);
384
+ });
385
+ }
386
+ return { ok: true, value: result };
387
+ } catch (err) {
388
+ const adminError = toAdminError(err);
389
+ if (mine === generation) error.value = adminError;
390
+ if (notifyOn !== "none") {
391
+ report(
392
+ () => notify?.(
393
+ "negative",
394
+ options.errorMessage?.(adminError) ?? adminErrorMessage(adminError, messages.value)
395
+ )
396
+ );
397
+ }
398
+ return { ok: false, error: adminError };
399
+ } finally {
400
+ if (--inFlight === 0) pending.value = false;
401
+ }
402
+ }
403
+ return {
404
+ run,
405
+ pending,
406
+ error,
407
+ reset: () => {
408
+ error.value = null;
409
+ }
410
+ };
411
+ }
412
+
413
+ // src/vue/use-async-data.ts
414
+ import { ref as ref4, watch as watch3 } from "vue";
415
+ function useAsyncData(fn, options) {
416
+ const data = ref4(options.initial);
417
+ const pending = ref4(false);
418
+ const error = ref4(null);
419
+ let generation = 0;
420
+ async function reload() {
421
+ const mine = ++generation;
422
+ pending.value = true;
423
+ error.value = null;
424
+ try {
425
+ const result = await fn();
426
+ if (mine !== generation) return;
427
+ data.value = result;
428
+ } catch (err) {
429
+ if (mine !== generation) return;
430
+ error.value = toAdminError(err);
431
+ data.value = options.initial;
432
+ } finally {
433
+ if (mine === generation) pending.value = false;
434
+ }
435
+ }
436
+ if (options.watch?.length) {
437
+ watch3(options.watch, () => void reload());
438
+ }
439
+ if (options.immediate !== false) {
440
+ void Promise.resolve().then(() => reload());
441
+ }
442
+ return { data, pending, error, reload };
443
+ }
444
+
445
+ // src/vue/use-resource-list.ts
446
+ import { computed as computed3, ref as ref5, watch as watch4 } from "vue";
447
+ var DEFAULT_LIST_OP = "list";
448
+ var EMPTY_PAGE = Object.freeze({
449
+ items: Object.freeze([]),
450
+ total: 0
451
+ });
452
+ function paginationCollision(key, op, claimed) {
453
+ return new AdminError({
454
+ message: `useResourceList("${key}", { op: "${op}" }): the filter carries ${claimed.join(" and ")}, which the list already sends. The filter is serialised after the pagination, so its value wins on the wire while page.value keeps the one goToPage() set \u2014 the list would report a page it is not showing. Drive the page with goToPage()/setPageSize() and leave those keys out of the filter.`
455
+ });
456
+ }
457
+ function paginationKeysIn(filter) {
458
+ return LIST_PAGINATION_PARAMS.filter((param) => isSentInQuery(filter[param]));
459
+ }
460
+ function readListOp(key, ops, name) {
461
+ const op = Object.hasOwn(ops, name) ? ops[name] : void 0;
462
+ if (typeof op !== "function") {
463
+ throw new Error(
464
+ `useResourceList("${key}", { op: "${name}" }): that resource has no such operation. It offers: ${Object.keys(ops).join(", ")}.`
465
+ );
466
+ }
467
+ return op;
468
+ }
469
+ function useResourceList(key, opts) {
470
+ const ops = useResource(key);
471
+ const opName = opts?.op ?? DEFAULT_LIST_OP;
472
+ const load = readListOp(key, ops, opName);
473
+ if (opts?.filter) {
474
+ const claimed = paginationKeysIn(opts.filter.value);
475
+ if (claimed.length > 0) throw paginationCollision(key, opName, claimed);
476
+ }
477
+ const page = ref5(LIST_FIRST_PAGE);
478
+ const pageSize = ref5(clampListPageSize(opts?.pageSize ?? LIST_PAGE_SIZE_DEFAULT));
479
+ function query() {
480
+ const filter = opts?.filter?.value;
481
+ if (filter) {
482
+ const claimed = paginationKeysIn(filter);
483
+ if (claimed.length > 0) throw paginationCollision(key, opName, claimed);
484
+ }
485
+ return { page: page.value, pageSize: pageSize.value, filter };
486
+ }
487
+ const state = useAsyncData(() => load(query()), {
488
+ initial: EMPTY_PAGE,
489
+ immediate: opts?.immediate
490
+ });
491
+ watch4(state.data, (answer) => {
492
+ if (typeof answer.page === "number") page.value = answer.page;
493
+ if (typeof answer.pageSize === "number") pageSize.value = answer.pageSize;
494
+ });
495
+ async function goToPage(next) {
496
+ page.value = clampListPage(next);
497
+ await state.reload();
498
+ }
499
+ async function setPageSize(size) {
500
+ pageSize.value = clampListPageSize(size);
501
+ page.value = LIST_FIRST_PAGE;
502
+ await state.reload();
503
+ }
504
+ if (opts?.filter) {
505
+ watch4(
506
+ opts.filter,
507
+ () => {
508
+ page.value = LIST_FIRST_PAGE;
509
+ void state.reload();
510
+ },
511
+ { deep: true }
512
+ );
513
+ }
514
+ return {
515
+ items: computed3(() => state.data.value.items),
516
+ total: computed3(() => state.data.value.total ?? state.data.value.items.length),
517
+ page,
518
+ pageSize,
519
+ pending: state.pending,
520
+ error: state.error,
521
+ reload: state.reload,
522
+ goToPage,
523
+ setPageSize
524
+ };
525
+ }
526
+
323
527
  // src/vue/use-tenants.ts
324
- import { ref as ref3 } from "vue";
528
+ import { ref as ref6 } from "vue";
325
529
  function useTenants(options) {
326
530
  if (!options?.endpoint) {
327
531
  throw new Error(
328
532
  'useTenants: `endpoint` is required (e.g. "/api/v1/admin/tenants" or "/api/admin/tenants"). The platform has no default because apps use different globalPrefix conventions.'
329
533
  );
330
534
  }
331
- const filter = options.filter ?? ref3({});
535
+ const filter = options.filter ?? ref6({});
332
536
  const list = useApiList({
333
537
  endpoint: options.endpoint,
334
538
  filter,
@@ -340,14 +544,14 @@ function useTenants(options) {
340
544
  }
341
545
 
342
546
  // src/vue/use-audit-entries.ts
343
- import { ref as ref4 } from "vue";
547
+ import { ref as ref7 } from "vue";
344
548
  function useAuditEntries(options) {
345
549
  if (!options?.endpoint) {
346
550
  throw new Error(
347
551
  'useAuditEntries: `endpoint` is required (e.g. "/api/admin/audit" or "/api/v1/admin/audit"). The platform has no default because apps use different globalPrefix conventions.'
348
552
  );
349
553
  }
350
- const filter = options.filter ?? ref4({});
554
+ const filter = options.filter ?? ref7({});
351
555
  const list = useApiList({
352
556
  endpoint: options.endpoint,
353
557
  filter,
@@ -359,7 +563,7 @@ function useAuditEntries(options) {
359
563
  }
360
564
 
361
565
  // src/vue/use-entitlement.ts
362
- import { ref as ref5 } from "vue";
566
+ import { ref as ref8 } from "vue";
363
567
  function useEntitlement(options) {
364
568
  if (!options?.endpoint) {
365
569
  throw new Error(
@@ -368,9 +572,9 @@ function useEntitlement(options) {
368
572
  }
369
573
  const endpoint = options.endpoint;
370
574
  const http = options.http ?? defaultHttpClient();
371
- const entitlement = ref5(null);
372
- const loading = ref5(false);
373
- const error = ref5(null);
575
+ const entitlement = ref8(null);
576
+ const loading = ref8(false);
577
+ const error = ref8(null);
374
578
  async function load() {
375
579
  loading.value = true;
376
580
  error.value = null;
@@ -400,15 +604,15 @@ function useEntitlement(options) {
400
604
  }
401
605
 
402
606
  // src/vue/use-tenant-manifest.ts
403
- import { ref as ref6 } from "vue";
607
+ import { ref as ref9 } from "vue";
404
608
  function useTenantManifest(options) {
405
609
  if (!options?.endpoint) {
406
610
  throw new Error('useTenantManifest: `endpoint` is required (e.g. "/api/tenant/manifest").');
407
611
  }
408
612
  const http = options.http ?? defaultHttpClient();
409
- const manifest = ref6(null);
410
- const loading = ref6(false);
411
- const error = ref6(null);
613
+ const manifest = ref9(null);
614
+ const loading = ref9(false);
615
+ const error = ref9(null);
412
616
  async function load() {
413
617
  loading.value = true;
414
618
  error.value = null;
@@ -442,7 +646,7 @@ function useTenantManifest(options) {
442
646
  }
443
647
 
444
648
  // src/vue/entitlement-provider.ts
445
- import { inject as inject3 } from "vue";
649
+ import { inject as inject4 } from "vue";
446
650
  var ENTITLEMENT_INJECTION_KEY = /* @__PURE__ */ Symbol.for(
447
651
  "@saasicat/ui-vue/ENTITLEMENT"
448
652
  );
@@ -450,7 +654,7 @@ function provideEntitlement(app, entitlement) {
450
654
  app.provide(ENTITLEMENT_INJECTION_KEY, entitlement);
451
655
  }
452
656
  function useInjectedEntitlement() {
453
- return inject3(ENTITLEMENT_INJECTION_KEY, null);
657
+ return inject4(ENTITLEMENT_INJECTION_KEY, null);
454
658
  }
455
659
 
456
660
  // src/vue/feature-router-guard.ts
@@ -479,15 +683,15 @@ function buildFeatureRouterGuard(options) {
479
683
  }
480
684
 
481
685
  // src/vue/use-tenant-billing-catalog.ts
482
- import { ref as ref7 } from "vue";
686
+ import { ref as ref10 } from "vue";
483
687
  function useTenantBillingCatalog(options = {}) {
484
688
  const apiPrefix = trimTrailingSlashes(options.apiPrefix ?? "/billing");
485
689
  const http = options.http ?? defaultHttpClient();
486
- const plans = ref7(null);
487
- const featureRegistry = ref7(null);
488
- const bundles = ref7(null);
489
- const loading = ref7(false);
490
- const error = ref7(null);
690
+ const plans = ref10(null);
691
+ const featureRegistry = ref10(null);
692
+ const bundles = ref10(null);
693
+ const loading = ref10(false);
694
+ const error = ref10(null);
491
695
  async function fetchJson(path) {
492
696
  const res = await http(`${apiPrefix}${path}`, { method: "GET" });
493
697
  if (res.status !== 200) {
@@ -537,14 +741,14 @@ function useTenantBillingCatalog(options = {}) {
537
741
  }
538
742
 
539
743
  // src/vue/use-tenant-billing.ts
540
- import { ref as ref8 } from "vue";
744
+ import { ref as ref11 } from "vue";
541
745
  function useTenantBilling(options = {}) {
542
746
  const apiPrefix = trimTrailingSlashes(options.apiPrefix ?? "/billing");
543
747
  const http = options.http ?? defaultHttpClient();
544
- const usage = ref8(null);
545
- const loading = ref8(false);
546
- const error = ref8(null);
547
- const subscriptionBundles = ref8([]);
748
+ const usage = ref11(null);
749
+ const loading = ref11(false);
750
+ const error = ref11(null);
751
+ const subscriptionBundles = ref11([]);
548
752
  function authHeaders() {
549
753
  const headers = {};
550
754
  const token = options.getAuthToken?.();
@@ -678,7 +882,7 @@ function useTenantBilling(options = {}) {
678
882
  }
679
883
 
680
884
  // src/vue/use-subscription-draft.ts
681
- import { computed as computed3, ref as ref9 } from "vue";
885
+ import { computed as computed4, ref as ref12 } from "vue";
682
886
  import {
683
887
  selectChargeableBundles
684
888
  } from "@saasicat/types";
@@ -697,27 +901,27 @@ function priceForCycle(monthlyNet, yearlyNet, cycle, yearlyFactor) {
697
901
  }
698
902
  function useSubscriptionDraft(options) {
699
903
  const yearlyFactor = options.yearlyFactor ?? DEFAULT_YEARLY_FACTOR;
700
- const plansRef = computed3(() => unwrap(options.plans) ?? []);
701
- const subscriptionBundlesRef = computed3(
904
+ const plansRef = computed4(() => unwrap(options.plans) ?? []);
905
+ const subscriptionBundlesRef = computed4(
702
906
  () => unwrap(options.subscriptionBundles ?? null) ?? []
703
907
  );
704
- const plan = ref9(options.initialPlan ?? null);
705
- const cycle = ref9(options.initialCycle ?? "MONTHLY");
706
- const selectedBundleVersionIds = ref9(
908
+ const plan = ref12(options.initialPlan ?? null);
909
+ const cycle = ref12(options.initialCycle ?? "MONTHLY");
910
+ const selectedBundleVersionIds = ref12(
707
911
  new Set(options.initialBundleVersionIds ?? [])
708
912
  );
709
- const promoCode = ref9("");
710
- const promoState = ref9({ status: "idle", preview: null, message: "" });
711
- const selectedPlan = computed3(() => {
913
+ const promoCode = ref12("");
914
+ const promoState = ref12({ status: "idle", preview: null, message: "" });
915
+ const selectedPlan = computed4(() => {
712
916
  if (!plan.value) return null;
713
917
  return plansRef.value.find((p) => p.id === plan.value) ?? null;
714
918
  });
715
- const selectedBundles = computed3(
919
+ const selectedBundles = computed4(
716
920
  () => subscriptionBundlesRef.value.filter(
717
921
  (b) => selectedBundleVersionIds.value.has(b.bundleVersionId)
718
922
  )
719
923
  );
720
- const chargeableBundles = computed3(() => {
924
+ const chargeableBundles = computed4(() => {
721
925
  const planFeatures = selectedPlan.value?.features ?? [];
722
926
  const selected = selectedBundles.value;
723
927
  const keptIds = new Set(
@@ -725,7 +929,7 @@ function useSubscriptionDraft(options) {
725
929
  );
726
930
  return selected.filter((b) => keptIds.has(b.bundleVersionId));
727
931
  });
728
- const activeFeatures = computed3(() => {
932
+ const activeFeatures = computed4(() => {
729
933
  const result = /* @__PURE__ */ new Set();
730
934
  const planObj = selectedPlan.value;
731
935
  if (planObj) {
@@ -736,7 +940,7 @@ function useSubscriptionDraft(options) {
736
940
  }
737
941
  return result;
738
942
  });
739
- const pricing = computed3(() => {
943
+ const pricing = computed4(() => {
740
944
  const cyc = cycle.value;
741
945
  const planObj = selectedPlan.value;
742
946
  const planNet = planObj ? priceForCycle(planObj.monthlyNet ?? 0, planObj.yearlyNet, cyc, yearlyFactor) : 0;
@@ -796,7 +1000,7 @@ function useSubscriptionDraft(options) {
796
1000
  }
797
1001
  };
798
1002
  });
799
- const isDirty = computed3(() => {
1003
+ const isDirty = computed4(() => {
800
1004
  return selectedBundleVersionIds.value.size > 0 || promoState.value.status === "valid";
801
1005
  });
802
1006
  function setPlan(planId) {
@@ -872,7 +1076,7 @@ function useSubscriptionDraft(options) {
872
1076
  }
873
1077
 
874
1078
  // src/vue/use-bulk-publish.ts
875
- import { computed as computed4, ref as ref10 } from "vue";
1079
+ import { computed as computed5, ref as ref13 } from "vue";
876
1080
  function useBulkPublish(options) {
877
1081
  const msg = useSaMessages("planVersions");
878
1082
  if (!options?.endpoints) {
@@ -882,15 +1086,15 @@ function useBulkPublish(options) {
882
1086
  }
883
1087
  const http = options.http ?? defaultHttpClient();
884
1088
  const endpoints = options.endpoints;
885
- const items = ref10([]);
886
- const successCount = computed4(() => items.value.filter((i) => i.status === "published").length);
887
- const failureCount = computed4(() => items.value.filter((i) => i.status === "failed").length);
888
- const progress = computed4(() => {
1089
+ const items = ref13([]);
1090
+ const successCount = computed5(() => items.value.filter((i) => i.status === "published").length);
1091
+ const failureCount = computed5(() => items.value.filter((i) => i.status === "failed").length);
1092
+ const progress = computed5(() => {
889
1093
  if (items.value.length === 0) return 0;
890
1094
  const done2 = successCount.value + failureCount.value;
891
1095
  return done2 / items.value.length;
892
1096
  });
893
- const done = computed4(
1097
+ const done = computed5(
894
1098
  () => items.value.length > 0 && items.value.every((i) => i.status === "published" || i.status === "failed")
895
1099
  );
896
1100
  function setItems(next) {
@@ -948,7 +1152,7 @@ function useBulkPublish(options) {
948
1152
  }
949
1153
 
950
1154
  // src/vue/use-plan-editor.ts
951
- import { computed as computed5, ref as ref11 } from "vue";
1155
+ import { computed as computed6, ref as ref14 } from "vue";
952
1156
  var PlannedOnlyFeatureError = class extends Error {
953
1157
  constructor(violations) {
954
1158
  super(
@@ -968,10 +1172,10 @@ function tierWeight(tier) {
968
1172
  function usePlanEditor(manifest, options = {}) {
969
1173
  const baseSet = new Set(options.baseFeatures ?? []);
970
1174
  const nonRegressive = options.nonRegressive ?? true;
971
- const selectedFeatures = ref11(new Set(options.initialFeatures ?? []));
1175
+ const selectedFeatures = ref14(new Set(options.initialFeatures ?? []));
972
1176
  const catalogFeatures = manifest.planCatalogSnapshot.features ?? [];
973
1177
  const plannedOnlySet = new Set(catalogFeatures.filter((f) => f.plannedOnly).map((f) => f.key));
974
- const availableFeatures = computed5(() => {
1178
+ const availableFeatures = computed6(() => {
975
1179
  return catalogFeatures.map((def) => {
976
1180
  const isSelected = selectedFeatures.value.has(def.key);
977
1181
  const isPlannedOnly = !!def.plannedOnly;
@@ -980,7 +1184,7 @@ function usePlanEditor(manifest, options = {}) {
980
1184
  return { def, isSelected, isPlannedOnly, isInherited, canToggle };
981
1185
  });
982
1186
  });
983
- const featuresByTier = computed5(() => {
1187
+ const featuresByTier = computed6(() => {
984
1188
  const groups = /* @__PURE__ */ new Map();
985
1189
  for (const row of availableFeatures.value) {
986
1190
  const tier = row.def.tier ?? "OTHER";
@@ -1025,12 +1229,12 @@ function usePlanEditor(manifest, options = {}) {
1025
1229
  }
1026
1230
 
1027
1231
  // src/vue/use-public-boot.ts
1028
- import { ref as ref12 } from "vue";
1232
+ import { ref as ref15 } from "vue";
1029
1233
  function usePublicBoot(options) {
1030
1234
  const loader = new BootLoader(options);
1031
- const boot = ref12(null);
1032
- const loading = ref12(false);
1033
- const error = ref12(null);
1235
+ const boot = ref15(null);
1236
+ const loading = ref15(false);
1237
+ const error = ref15(null);
1034
1238
  async function load() {
1035
1239
  loading.value = true;
1036
1240
  error.value = null;
@@ -1047,27 +1251,27 @@ function usePublicBoot(options) {
1047
1251
  }
1048
1252
 
1049
1253
  // src/vue/use-discovery.ts
1050
- import { ref as ref13 } from "vue";
1254
+ import { ref as ref16 } from "vue";
1051
1255
  var DiscoveryLoadError = class extends Error {
1052
1256
  constructor(status, message) {
1053
1257
  super(message);
1054
1258
  this.status = status;
1055
1259
  this.name = "DiscoveryLoadError";
1260
+ markPlatformError(this);
1056
1261
  }
1057
1262
  status;
1058
1263
  };
1059
1264
  function useDiscovery(options) {
1060
- const msg = useSaMessages("discovery");
1061
1265
  if (!options?.endpoint) {
1062
1266
  throw new Error(
1063
1267
  'useDiscovery: `endpoint` is required (e.g. "/api/admin/discovery" or "/api/v1/admin/discovery"). The platform has no default because apps use different globalPrefix conventions.'
1064
1268
  );
1065
1269
  }
1066
1270
  const http = options.http ?? defaultHttpClient();
1067
- const snapshot = ref13(null);
1068
- const etag = ref13(null);
1069
- const loading = ref13(false);
1070
- const error = ref13(null);
1271
+ const snapshot = ref16(null);
1272
+ const etag = ref16(null);
1273
+ const loading = ref16(false);
1274
+ const error = ref16(null);
1071
1275
  async function fetchSnapshot(includeEtag) {
1072
1276
  loading.value = true;
1073
1277
  error.value = null;
@@ -1079,13 +1283,19 @@ function useDiscovery(options) {
1079
1283
  headers["If-None-Match"] = etag.value;
1080
1284
  }
1081
1285
  const res = await http(options.endpoint, { method: "GET", headers });
1286
+ requireServerAnswer(
1287
+ res.status,
1288
+ "GET",
1289
+ options.endpoint,
1290
+ (diagnostic) => new DiscoveryLoadError(res.status, diagnostic)
1291
+ );
1082
1292
  if (res.status === 304) {
1083
1293
  return;
1084
1294
  }
1085
1295
  if (res.status !== 200) {
1086
1296
  throw new DiscoveryLoadError(
1087
1297
  res.status,
1088
- formatMessage(msg.value.errorDiscoveryHttp, { status: res.status })
1298
+ `Discovery endpoint responded with HTTP ${res.status}`
1089
1299
  );
1090
1300
  }
1091
1301
  const body = await res.json();
@@ -1111,11 +1321,18 @@ function useDiscovery(options) {
1111
1321
  const headers = { "content-type": "application/json" };
1112
1322
  const token = options.getAuthToken?.();
1113
1323
  if (token) headers.Authorization = `Bearer ${token}`;
1114
- const res = await http(`${options.endpoint}/rescan`, { method: "POST", headers });
1324
+ const rescanUrl = `${options.endpoint}/rescan`;
1325
+ const res = await http(rescanUrl, { method: "POST", headers });
1326
+ requireServerAnswer(
1327
+ res.status,
1328
+ "POST",
1329
+ rescanUrl,
1330
+ (diagnostic) => new DiscoveryLoadError(res.status, diagnostic)
1331
+ );
1115
1332
  if (res.status !== 200 && res.status !== 201) {
1116
1333
  throw new DiscoveryLoadError(
1117
1334
  res.status,
1118
- formatMessage(msg.value.errorRescanHttp, { status: res.status })
1335
+ `Discovery rescan responded with HTTP ${res.status}`
1119
1336
  );
1120
1337
  }
1121
1338
  const body = await res.json();
@@ -1134,13 +1351,14 @@ function useDiscovery(options) {
1134
1351
  }
1135
1352
 
1136
1353
  // src/vue/use-catalog-entries.ts
1137
- import { ref as ref14 } from "vue";
1354
+ import { ref as ref17 } from "vue";
1138
1355
  var CatalogEntriesApiError = class extends Error {
1139
1356
  constructor(status, body, message) {
1140
1357
  super(message);
1141
1358
  this.status = status;
1142
1359
  this.body = body;
1143
1360
  this.name = "CatalogEntriesApiError";
1361
+ markPlatformError(this);
1144
1362
  }
1145
1363
  status;
1146
1364
  body;
@@ -1152,13 +1370,12 @@ function useCatalogEntries(options) {
1152
1370
  if (!options?.projectKey) {
1153
1371
  throw new Error("useCatalogEntries: `projectKey` is required.");
1154
1372
  }
1155
- const msg = useSaMessages("discovery");
1156
1373
  const http = options.http ?? defaultHttpClient();
1157
- const capabilities = ref14([]);
1158
- const features = ref14([]);
1159
- const quotas = ref14([]);
1160
- const loading = ref14(false);
1161
- const error = ref14(null);
1374
+ const capabilities = ref17([]);
1375
+ const features = ref17([]);
1376
+ const quotas = ref17([]);
1377
+ const loading = ref17(false);
1378
+ const error = ref17(null);
1162
1379
  const base = `${options.adminEndpoint}/catalog`;
1163
1380
  const pk = encodeURIComponent(options.projectKey);
1164
1381
  function authHeaders() {
@@ -1166,18 +1383,25 @@ function useCatalogEntries(options) {
1166
1383
  return token ? { Authorization: `Bearer ${token}` } : {};
1167
1384
  }
1168
1385
  async function fetchJson(url, init) {
1386
+ const method = init?.method ?? "GET";
1169
1387
  const res = await http(url, {
1170
- method: init?.method ?? "GET",
1388
+ method,
1171
1389
  headers: { "content-type": "application/json", ...authHeaders(), ...init?.headers },
1172
1390
  body: init?.body
1173
1391
  });
1392
+ requireServerAnswer(
1393
+ res.status,
1394
+ method,
1395
+ url,
1396
+ (diagnostic) => new CatalogEntriesApiError(res.status, null, diagnostic)
1397
+ );
1174
1398
  if (res.status === 204) return null;
1175
1399
  const body = await res.json().catch(() => null);
1176
1400
  if (res.status >= 400) {
1177
1401
  throw new CatalogEntriesApiError(
1178
1402
  res.status,
1179
1403
  body,
1180
- formatMessage(msg.value.errorCatalogHttp, { status: res.status })
1404
+ `Catalog entries API responded with HTTP ${res.status}`
1181
1405
  );
1182
1406
  }
1183
1407
  return body;
@@ -1205,7 +1429,8 @@ function useCatalogEntries(options) {
1205
1429
  `${base}/features/${encodeURIComponent(featureKey)}/review?projectKey=${pk}`,
1206
1430
  { method: "PATCH", body: JSON.stringify(data) }
1207
1431
  );
1208
- if (!updated) throw new CatalogEntriesApiError(0, null, "Review returned no body");
1432
+ if (!updated)
1433
+ throw markEmptyResponse(new CatalogEntriesApiError(0, null, "Review returned no body"));
1209
1434
  features.value = features.value.map((f) => f.featureKey === featureKey ? updated : f);
1210
1435
  return updated;
1211
1436
  }
@@ -1214,7 +1439,8 @@ function useCatalogEntries(options) {
1214
1439
  `${base}/quotas/${encodeURIComponent(quotaKey)}/review?projectKey=${pk}`,
1215
1440
  { method: "PATCH", body: JSON.stringify(data) }
1216
1441
  );
1217
- if (!updated) throw new CatalogEntriesApiError(0, null, "Review returned no body");
1442
+ if (!updated)
1443
+ throw markEmptyResponse(new CatalogEntriesApiError(0, null, "Review returned no body"));
1218
1444
  quotas.value = quotas.value.map((q) => q.quotaKey === quotaKey ? updated : q);
1219
1445
  return updated;
1220
1446
  }
@@ -1223,7 +1449,8 @@ function useCatalogEntries(options) {
1223
1449
  `${base}/features/${encodeURIComponent(featureKey)}/i18n?projectKey=${pk}`,
1224
1450
  { method: "PATCH", body: JSON.stringify({ i18n }) }
1225
1451
  );
1226
- if (!updated) throw new CatalogEntriesApiError(0, null, "i18n returned no body");
1452
+ if (!updated)
1453
+ throw markEmptyResponse(new CatalogEntriesApiError(0, null, "i18n returned no body"));
1227
1454
  features.value = features.value.map((f) => f.featureKey === featureKey ? updated : f);
1228
1455
  return updated;
1229
1456
  }
@@ -1232,7 +1459,8 @@ function useCatalogEntries(options) {
1232
1459
  `${base}/quotas/${encodeURIComponent(quotaKey)}/i18n?projectKey=${pk}`,
1233
1460
  { method: "PATCH", body: JSON.stringify({ i18n }) }
1234
1461
  );
1235
- if (!updated) throw new CatalogEntriesApiError(0, null, "i18n returned no body");
1462
+ if (!updated)
1463
+ throw markEmptyResponse(new CatalogEntriesApiError(0, null, "i18n returned no body"));
1236
1464
  quotas.value = quotas.value.map((q) => q.quotaKey === quotaKey ? updated : q);
1237
1465
  return updated;
1238
1466
  }
@@ -1241,7 +1469,8 @@ function useCatalogEntries(options) {
1241
1469
  `${base}/features/${encodeURIComponent(featureKey)}?projectKey=${pk}`,
1242
1470
  { method: "PATCH", body: JSON.stringify(data) }
1243
1471
  );
1244
- if (!updated) throw new CatalogEntriesApiError(0, null, "Base returned no body");
1472
+ if (!updated)
1473
+ throw markEmptyResponse(new CatalogEntriesApiError(0, null, "Base returned no body"));
1245
1474
  features.value = features.value.map((f) => f.featureKey === featureKey ? updated : f);
1246
1475
  return updated;
1247
1476
  }
@@ -1250,7 +1479,8 @@ function useCatalogEntries(options) {
1250
1479
  `${base}/quotas/${encodeURIComponent(quotaKey)}?projectKey=${pk}`,
1251
1480
  { method: "PATCH", body: JSON.stringify(data) }
1252
1481
  );
1253
- if (!updated) throw new CatalogEntriesApiError(0, null, "Base returned no body");
1482
+ if (!updated)
1483
+ throw markEmptyResponse(new CatalogEntriesApiError(0, null, "Base returned no body"));
1254
1484
  quotas.value = quotas.value.map((q) => q.quotaKey === quotaKey ? updated : q);
1255
1485
  return updated;
1256
1486
  }
@@ -1259,7 +1489,8 @@ function useCatalogEntries(options) {
1259
1489
  method: "POST",
1260
1490
  body: JSON.stringify({ snapshot })
1261
1491
  });
1262
- if (!result) throw new CatalogEntriesApiError(0, null, "Sync returned no body");
1492
+ if (!result)
1493
+ throw markEmptyResponse(new CatalogEntriesApiError(0, null, "Sync returned no body"));
1263
1494
  await load();
1264
1495
  return result;
1265
1496
  }
@@ -1284,13 +1515,14 @@ function useCatalogEntries(options) {
1284
1515
  }
1285
1516
 
1286
1517
  // src/vue/use-bundles.ts
1287
- import { ref as ref15 } from "vue";
1518
+ import { ref as ref18 } from "vue";
1288
1519
  var BundlesApiError = class extends Error {
1289
1520
  constructor(status, body, message) {
1290
1521
  super(message);
1291
1522
  this.status = status;
1292
1523
  this.body = body;
1293
1524
  this.name = "BundlesApiError";
1525
+ markPlatformError(this);
1294
1526
  }
1295
1527
  status;
1296
1528
  body;
@@ -1305,20 +1537,27 @@ function useBundles(options) {
1305
1537
  throw new Error("useBundles: `projectKey` is required.");
1306
1538
  }
1307
1539
  const http = options.http ?? defaultHttpClient();
1308
- const bundles = ref15([]);
1309
- const loading = ref15(false);
1310
- const error = ref15(null);
1540
+ const bundles = ref18([]);
1541
+ const loading = ref18(false);
1542
+ const error = ref18(null);
1311
1543
  const baseUrl = `${options.adminEndpoint}/catalog/bundles`;
1312
1544
  function authHeaders() {
1313
1545
  const token = options.getAuthToken?.();
1314
1546
  return token ? { Authorization: `Bearer ${token}` } : {};
1315
1547
  }
1316
1548
  async function fetchJson(url, init) {
1549
+ const method = init?.method ?? "GET";
1317
1550
  const res = await http(url, {
1318
- method: init?.method ?? "GET",
1551
+ method,
1319
1552
  headers: { "content-type": "application/json", ...authHeaders(), ...init?.headers },
1320
1553
  body: init?.body
1321
1554
  });
1555
+ requireServerAnswer(
1556
+ res.status,
1557
+ method,
1558
+ url,
1559
+ (diagnostic) => new BundlesApiError(res.status, null, diagnostic)
1560
+ );
1322
1561
  if (res.status === 204) return null;
1323
1562
  const body = await res.json().catch(() => null);
1324
1563
  if (res.status >= 400) {
@@ -1349,7 +1588,8 @@ function useBundles(options) {
1349
1588
  method: "POST",
1350
1589
  body: JSON.stringify(data)
1351
1590
  });
1352
- if (!created) throw new BundlesApiError(0, null, "Create returned no body");
1591
+ if (!created)
1592
+ throw markEmptyResponse(new BundlesApiError(0, null, "Create returned no body"));
1353
1593
  bundles.value = [...bundles.value, created];
1354
1594
  return created;
1355
1595
  }
@@ -1358,7 +1598,8 @@ function useBundles(options) {
1358
1598
  method: "PATCH",
1359
1599
  body: JSON.stringify(data)
1360
1600
  });
1361
- if (!updated) throw new BundlesApiError(0, null, "Update returned no body");
1601
+ if (!updated)
1602
+ throw markEmptyResponse(new BundlesApiError(0, null, "Update returned no body"));
1362
1603
  bundles.value = bundles.value.map((b) => b.id === bundleId ? updated : b);
1363
1604
  return updated;
1364
1605
  }
@@ -1379,9 +1620,9 @@ function useBundleVersions(options) {
1379
1620
  throw new Error("useBundleVersions: `bundleId` is required.");
1380
1621
  }
1381
1622
  const http = options.http ?? defaultHttpClient();
1382
- const versions = ref15([]);
1383
- const loading = ref15(false);
1384
- const error = ref15(null);
1623
+ const versions = ref18([]);
1624
+ const loading = ref18(false);
1625
+ const error = ref18(null);
1385
1626
  const bundleVersionsUrl = `${options.adminEndpoint}/catalog/bundles/${options.bundleId}/versions`;
1386
1627
  const versionUrlBase = `${options.adminEndpoint}/catalog/bundle-versions`;
1387
1628
  function authHeaders() {
@@ -1389,11 +1630,18 @@ function useBundleVersions(options) {
1389
1630
  return token ? { Authorization: `Bearer ${token}` } : {};
1390
1631
  }
1391
1632
  async function fetchJson(url, init) {
1633
+ const method = init?.method ?? "GET";
1392
1634
  const res = await http(url, {
1393
- method: init?.method ?? "GET",
1635
+ method,
1394
1636
  headers: { "content-type": "application/json", ...authHeaders(), ...init?.headers },
1395
1637
  body: init?.body
1396
1638
  });
1639
+ requireServerAnswer(
1640
+ res.status,
1641
+ method,
1642
+ url,
1643
+ (diagnostic) => new BundlesApiError(res.status, null, diagnostic)
1644
+ );
1397
1645
  if (res.status === 204) return null;
1398
1646
  const body = await res.json().catch(() => null);
1399
1647
  if (res.status >= 400) {
@@ -1422,7 +1670,8 @@ function useBundleVersions(options) {
1422
1670
  method: "POST",
1423
1671
  body: JSON.stringify(data)
1424
1672
  });
1425
- if (!result) throw new BundlesApiError(0, null, "CreateDraft returned no body");
1673
+ if (!result)
1674
+ throw markEmptyResponse(new BundlesApiError(0, null, "CreateDraft returned no body"));
1426
1675
  versions.value = [...versions.value, result.bundleVersion];
1427
1676
  return result;
1428
1677
  }
@@ -1431,7 +1680,8 @@ function useBundleVersions(options) {
1431
1680
  `${versionUrlBase}/${versionId}`,
1432
1681
  { method: "PATCH", body: JSON.stringify(data) }
1433
1682
  );
1434
- if (!result) throw new BundlesApiError(0, null, "UpdateDraft returned no body");
1683
+ if (!result)
1684
+ throw markEmptyResponse(new BundlesApiError(0, null, "UpdateDraft returned no body"));
1435
1685
  versions.value = versions.value.map((v) => v.id === versionId ? result.bundleVersion : v);
1436
1686
  return result;
1437
1687
  }
@@ -1440,7 +1690,8 @@ function useBundleVersions(options) {
1440
1690
  `${versionUrlBase}/${versionId}/publish`,
1441
1691
  { method: "POST", body: JSON.stringify(opts) }
1442
1692
  );
1443
- if (!result) throw new BundlesApiError(0, null, "Publish returned no body");
1693
+ if (!result)
1694
+ throw markEmptyResponse(new BundlesApiError(0, null, "Publish returned no body"));
1444
1695
  await load();
1445
1696
  return result;
1446
1697
  }
@@ -1455,7 +1706,7 @@ function useBundleVersions(options) {
1455
1706
  }
1456
1707
 
1457
1708
  // src/vue/use-bundle-versions-map.ts
1458
- import { ref as ref16, watch as watch3 } from "vue";
1709
+ import { ref as ref19, watch as watch5 } from "vue";
1459
1710
  function useBundleVersionsMap(options) {
1460
1711
  if (!options?.adminEndpoint) {
1461
1712
  throw new Error("useBundleVersionsMap: `adminEndpoint` is required.");
@@ -1464,9 +1715,9 @@ function useBundleVersionsMap(options) {
1464
1715
  throw new Error("useBundleVersionsMap: `bundles` is required.");
1465
1716
  }
1466
1717
  const http = options.http ?? defaultHttpClient();
1467
- const versionsByBundle = ref16({});
1468
- const loading = ref16(false);
1469
- const error = ref16(null);
1718
+ const versionsByBundle = ref19({});
1719
+ const loading = ref19(false);
1720
+ const error = ref19(null);
1470
1721
  function authHeaders() {
1471
1722
  const token = options.getAuthToken?.();
1472
1723
  return token ? { Authorization: `Bearer ${token}` } : {};
@@ -1516,7 +1767,7 @@ function useBundleVersionsMap(options) {
1516
1767
  console.warn(`useBundleVersionsMap: refreshOne('${bundleId}') failed`, err);
1517
1768
  }
1518
1769
  }
1519
- watch3(
1770
+ watch5(
1520
1771
  () => options.bundles.value.map((b) => b.id).join(","),
1521
1772
  () => {
1522
1773
  if (options.bundles.value.length > 0) void refresh();
@@ -1528,13 +1779,14 @@ function useBundleVersionsMap(options) {
1528
1779
  }
1529
1780
 
1530
1781
  // src/vue/use-tenant-subscription-bundles.ts
1531
- import { ref as ref17 } from "vue";
1782
+ import { ref as ref20 } from "vue";
1532
1783
  var TenantSubscriptionBundlesApiError = class extends Error {
1533
1784
  constructor(status, body, message) {
1534
1785
  super(message);
1535
1786
  this.status = status;
1536
1787
  this.body = body;
1537
1788
  this.name = "TenantSubscriptionBundlesApiError";
1789
+ markPlatformError(this);
1538
1790
  }
1539
1791
  status;
1540
1792
  body;
@@ -1547,19 +1799,26 @@ function useTenantSubscriptionBundles(options) {
1547
1799
  }
1548
1800
  const http = options.http ?? defaultHttpClient();
1549
1801
  const baseUrl = `${options.billingEndpoint}/billing/subscription-bundles`;
1550
- const bundles = ref17([]);
1551
- const loading = ref17(false);
1552
- const error = ref17(null);
1802
+ const bundles = ref20([]);
1803
+ const loading = ref20(false);
1804
+ const error = ref20(null);
1553
1805
  function authHeaders() {
1554
1806
  const token = options.getAuthToken?.();
1555
1807
  return token ? { Authorization: `Bearer ${token}` } : {};
1556
1808
  }
1557
1809
  async function fetchJson(url, init) {
1810
+ const method = init?.method ?? "GET";
1558
1811
  const res = await http(url, {
1559
- method: init?.method ?? "GET",
1812
+ method,
1560
1813
  headers: { "content-type": "application/json", ...authHeaders(), ...init?.headers },
1561
1814
  body: init?.body
1562
1815
  });
1816
+ requireServerAnswer(
1817
+ res.status,
1818
+ method,
1819
+ url,
1820
+ (diagnostic) => new TenantSubscriptionBundlesApiError(res.status, null, diagnostic)
1821
+ );
1563
1822
  if (res.status === 204) return null;
1564
1823
  const body = await res.json().catch(() => null);
1565
1824
  if (res.status >= 400) {
@@ -1589,7 +1848,9 @@ function useTenantSubscriptionBundles(options) {
1589
1848
  body: JSON.stringify(data)
1590
1849
  });
1591
1850
  if (!result) {
1592
- throw new TenantSubscriptionBundlesApiError(0, null, "add returned no body");
1851
+ throw markEmptyResponse(
1852
+ new TenantSubscriptionBundlesApiError(0, null, "add returned no body")
1853
+ );
1593
1854
  }
1594
1855
  const hydrated = rehydrateDates(result);
1595
1856
  bundles.value = [hydrated, ...bundles.value];
@@ -1601,7 +1862,9 @@ function useTenantSubscriptionBundles(options) {
1601
1862
  { method: "DELETE", body: JSON.stringify(opts) }
1602
1863
  );
1603
1864
  if (!result) {
1604
- throw new TenantSubscriptionBundlesApiError(0, null, "cancel returned no body");
1865
+ throw markEmptyResponse(
1866
+ new TenantSubscriptionBundlesApiError(0, null, "cancel returned no body")
1867
+ );
1605
1868
  }
1606
1869
  const hydrated = rehydrateDates(result);
1607
1870
  bundles.value = bundles.value.map((b) => b.id === subscriptionBundleId ? hydrated : b);
@@ -1623,13 +1886,14 @@ function rehydrateDates(raw) {
1623
1886
  }
1624
1887
 
1625
1888
  // src/vue/use-marketing-projections.ts
1626
- import { ref as ref18 } from "vue";
1889
+ import { ref as ref21 } from "vue";
1627
1890
  var MarketingProjectionsApiError = class extends Error {
1628
1891
  constructor(status, body, message) {
1629
1892
  super(message);
1630
1893
  this.status = status;
1631
1894
  this.body = body;
1632
1895
  this.name = "MarketingProjectionsApiError";
1896
+ markPlatformError(this);
1633
1897
  }
1634
1898
  status;
1635
1899
  body;
@@ -1641,12 +1905,11 @@ function useMarketingProjections(options) {
1641
1905
  if (!options?.filter?.projectKey) {
1642
1906
  throw new Error("useMarketingProjections: `filter.projectKey` is required.");
1643
1907
  }
1644
- const msg = useSaMessages("marketing");
1645
1908
  const http = options.http ?? defaultHttpClient();
1646
- const projections = ref18([]);
1647
- const filter = ref18({ ...options.filter });
1648
- const loading = ref18(false);
1649
- const error = ref18(null);
1909
+ const projections = ref21([]);
1910
+ const filter = ref21({ ...options.filter });
1911
+ const loading = ref21(false);
1912
+ const error = ref21(null);
1650
1913
  const baseUrl = `${options.adminEndpoint}/catalog/marketing-projections`;
1651
1914
  function authHeaders() {
1652
1915
  const token = options.getAuthToken?.();
@@ -1662,18 +1925,25 @@ function useMarketingProjections(options) {
1662
1925
  return `${baseUrl}?${params.toString()}`;
1663
1926
  }
1664
1927
  async function fetchJson(url, init) {
1928
+ const method = init?.method ?? "GET";
1665
1929
  const res = await http(url, {
1666
- method: init?.method ?? "GET",
1930
+ method,
1667
1931
  headers: { "content-type": "application/json", ...authHeaders(), ...init?.headers },
1668
1932
  body: init?.body
1669
1933
  });
1934
+ requireServerAnswer(
1935
+ res.status,
1936
+ method,
1937
+ url,
1938
+ (diagnostic) => new MarketingProjectionsApiError(res.status, null, diagnostic)
1939
+ );
1670
1940
  if (res.status === 204) return null;
1671
1941
  const body = await res.json().catch(() => null);
1672
1942
  if (res.status >= 400) {
1673
1943
  throw new MarketingProjectionsApiError(
1674
1944
  res.status,
1675
1945
  body,
1676
- formatMessage(msg.value.errors.projectionsApi, { status: res.status })
1946
+ `Marketing projections API responded with HTTP ${res.status}`
1677
1947
  );
1678
1948
  }
1679
1949
  return body;
@@ -1700,7 +1970,9 @@ function useMarketingProjections(options) {
1700
1970
  body: JSON.stringify(data)
1701
1971
  });
1702
1972
  if (!created) {
1703
- throw new MarketingProjectionsApiError(0, null, "Create returned no body");
1973
+ throw markEmptyResponse(
1974
+ new MarketingProjectionsApiError(0, null, "Create returned no body")
1975
+ );
1704
1976
  }
1705
1977
  await load();
1706
1978
  return created;
@@ -1711,7 +1983,9 @@ function useMarketingProjections(options) {
1711
1983
  body: JSON.stringify(data)
1712
1984
  });
1713
1985
  if (!updated) {
1714
- throw new MarketingProjectionsApiError(0, null, "Update returned no body");
1986
+ throw markEmptyResponse(
1987
+ new MarketingProjectionsApiError(0, null, "Update returned no body")
1988
+ );
1715
1989
  }
1716
1990
  projections.value = projections.value.map((p) => p.id === id ? updated : p);
1717
1991
  return updated;
@@ -1727,13 +2001,14 @@ function useMarketingProjections(options) {
1727
2001
  }
1728
2002
 
1729
2003
  // src/vue/use-promotions.ts
1730
- import { ref as ref19 } from "vue";
2004
+ import { ref as ref22 } from "vue";
1731
2005
  var PromotionsApiError = class extends Error {
1732
2006
  constructor(status, body, message) {
1733
2007
  super(message);
1734
2008
  this.status = status;
1735
2009
  this.body = body;
1736
2010
  this.name = "PromotionsApiError";
2011
+ markPlatformError(this);
1737
2012
  }
1738
2013
  status;
1739
2014
  body;
@@ -1745,11 +2020,10 @@ function usePromotions(options) {
1745
2020
  if (!options?.projectKey) {
1746
2021
  throw new Error("usePromotions: `projectKey` is required.");
1747
2022
  }
1748
- const msg = useSaMessages("promos");
1749
2023
  const http = options.http ?? defaultHttpClient();
1750
- const promotions = ref19([]);
1751
- const loading = ref19(false);
1752
- const error = ref19(null);
2024
+ const promotions = ref22([]);
2025
+ const loading = ref22(false);
2026
+ const error = ref22(null);
1753
2027
  const baseUrl = `${options.adminEndpoint}/catalog/promotions`;
1754
2028
  const pk = encodeURIComponent(options.projectKey);
1755
2029
  function authHeaders() {
@@ -1757,18 +2031,25 @@ function usePromotions(options) {
1757
2031
  return token ? { Authorization: `Bearer ${token}` } : {};
1758
2032
  }
1759
2033
  async function fetchJson(url, init) {
2034
+ const method = init?.method ?? "GET";
1760
2035
  const res = await http(url, {
1761
- method: init?.method ?? "GET",
2036
+ method,
1762
2037
  headers: { "content-type": "application/json", ...authHeaders(), ...init?.headers },
1763
2038
  body: init?.body
1764
2039
  });
2040
+ requireServerAnswer(
2041
+ res.status,
2042
+ method,
2043
+ url,
2044
+ (diagnostic) => new PromotionsApiError(res.status, null, diagnostic)
2045
+ );
1765
2046
  if (res.status === 204) return null;
1766
2047
  const body = await res.json().catch(() => null);
1767
2048
  if (res.status >= 400) {
1768
2049
  throw new PromotionsApiError(
1769
2050
  res.status,
1770
2051
  body,
1771
- formatMessage(msg.value.apiErrorHttpStatus, { status: res.status })
2052
+ `Promotions API responded with HTTP ${res.status}`
1772
2053
  );
1773
2054
  }
1774
2055
  return body;
@@ -1790,7 +2071,8 @@ function usePromotions(options) {
1790
2071
  method: "POST",
1791
2072
  body: JSON.stringify(data)
1792
2073
  });
1793
- if (!created) throw new PromotionsApiError(0, null, "Create returned no body");
2074
+ if (!created)
2075
+ throw markEmptyResponse(new PromotionsApiError(0, null, "Create returned no body"));
1794
2076
  promotions.value = [...promotions.value, created];
1795
2077
  return created;
1796
2078
  }
@@ -1799,7 +2081,8 @@ function usePromotions(options) {
1799
2081
  method: "PATCH",
1800
2082
  body: JSON.stringify(data)
1801
2083
  });
1802
- if (!updated) throw new PromotionsApiError(0, null, "Update returned no body");
2084
+ if (!updated)
2085
+ throw markEmptyResponse(new PromotionsApiError(0, null, "Update returned no body"));
1803
2086
  promotions.value = promotions.value.map((p) => p.id === id ? updated : p);
1804
2087
  return updated;
1805
2088
  }
@@ -1814,13 +2097,14 @@ function usePromotions(options) {
1814
2097
  }
1815
2098
 
1816
2099
  // src/vue/use-plans.ts
1817
- import { ref as ref20 } from "vue";
2100
+ import { ref as ref23 } from "vue";
1818
2101
  var PlansApiError = class extends Error {
1819
2102
  constructor(status, body, message) {
1820
2103
  super(message);
1821
2104
  this.status = status;
1822
2105
  this.body = body;
1823
2106
  this.name = "PlansApiError";
2107
+ markPlatformError(this);
1824
2108
  }
1825
2109
  status;
1826
2110
  body;
@@ -1835,18 +2119,19 @@ function usePlans(options) {
1835
2119
  throw new Error("usePlans: `projectKey` is required.");
1836
2120
  }
1837
2121
  const http = options.http ?? defaultHttpClient();
1838
- const plans = ref20([]);
1839
- const loading = ref20(false);
1840
- const error = ref20(null);
1841
- const tenantCountsByPlanKey = ref20({});
2122
+ const plans = ref23([]);
2123
+ const loading = ref23(false);
2124
+ const error = ref23(null);
2125
+ const tenantCountsByPlanKey = ref23({});
1842
2126
  const baseUrl = `${options.adminEndpoint}/catalog/plans`;
1843
2127
  function authHeaders() {
1844
2128
  const token = options.getAuthToken?.();
1845
2129
  return token ? { Authorization: `Bearer ${token}` } : {};
1846
2130
  }
1847
2131
  async function fetchJson(url, init) {
2132
+ const method = init?.method ?? "GET";
1848
2133
  const res = await http(url, {
1849
- method: init?.method ?? "GET",
2134
+ method,
1850
2135
  headers: {
1851
2136
  "content-type": "application/json",
1852
2137
  ...authHeaders(),
@@ -1854,6 +2139,12 @@ function usePlans(options) {
1854
2139
  },
1855
2140
  body: init?.body
1856
2141
  });
2142
+ requireServerAnswer(
2143
+ res.status,
2144
+ method,
2145
+ url,
2146
+ (diagnostic) => new PlansApiError(res.status, null, diagnostic)
2147
+ );
1857
2148
  if (res.status === 204) return null;
1858
2149
  const body = await res.json().catch(() => null);
1859
2150
  if (res.status >= 400) {
@@ -1894,7 +2185,8 @@ function usePlans(options) {
1894
2185
  method: "POST",
1895
2186
  body: JSON.stringify(data)
1896
2187
  });
1897
- if (!created) throw new PlansApiError(0, null, "Create returned no body");
2188
+ if (!created)
2189
+ throw markEmptyResponse(new PlansApiError(0, null, "Create returned no body"));
1898
2190
  plans.value = [...plans.value, created];
1899
2191
  return created;
1900
2192
  }
@@ -1903,7 +2195,8 @@ function usePlans(options) {
1903
2195
  method: "PATCH",
1904
2196
  body: JSON.stringify(data)
1905
2197
  });
1906
- if (!updated) throw new PlansApiError(0, null, "Update returned no body");
2198
+ if (!updated)
2199
+ throw markEmptyResponse(new PlansApiError(0, null, "Update returned no body"));
1907
2200
  plans.value = plans.value.map((p) => p.id === planId ? updated : p);
1908
2201
  return updated;
1909
2202
  }
@@ -1939,9 +2232,9 @@ function usePlanVersions(options) {
1939
2232
  throw new Error("usePlanVersions: `planId` is required.");
1940
2233
  }
1941
2234
  const http = options.http ?? defaultHttpClient();
1942
- const versions = ref20([]);
1943
- const loading = ref20(false);
1944
- const error = ref20(null);
2235
+ const versions = ref23([]);
2236
+ const loading = ref23(false);
2237
+ const error = ref23(null);
1945
2238
  const planVersionsUrl = `${options.adminEndpoint}/catalog/plans/${options.planId}/versions`;
1946
2239
  const versionUrlBase = `${options.adminEndpoint}/catalog/plan-versions`;
1947
2240
  function authHeaders() {
@@ -1949,8 +2242,9 @@ function usePlanVersions(options) {
1949
2242
  return token ? { Authorization: `Bearer ${token}` } : {};
1950
2243
  }
1951
2244
  async function fetchJson(url, init) {
2245
+ const method = init?.method ?? "GET";
1952
2246
  const res = await http(url, {
1953
- method: init?.method ?? "GET",
2247
+ method,
1954
2248
  headers: {
1955
2249
  "content-type": "application/json",
1956
2250
  ...authHeaders(),
@@ -1958,6 +2252,12 @@ function usePlanVersions(options) {
1958
2252
  },
1959
2253
  body: init?.body
1960
2254
  });
2255
+ requireServerAnswer(
2256
+ res.status,
2257
+ method,
2258
+ url,
2259
+ (diagnostic) => new PlansApiError(res.status, null, diagnostic)
2260
+ );
1961
2261
  if (res.status === 204) return null;
1962
2262
  const body = await res.json().catch(() => null);
1963
2263
  if (res.status >= 400) {
@@ -1986,7 +2286,8 @@ function usePlanVersions(options) {
1986
2286
  method: "POST",
1987
2287
  body: JSON.stringify(data)
1988
2288
  });
1989
- if (!result) throw new PlansApiError(0, null, "CreateDraft returned no body");
2289
+ if (!result)
2290
+ throw markEmptyResponse(new PlansApiError(0, null, "CreateDraft returned no body"));
1990
2291
  versions.value = [...versions.value, result.planVersion];
1991
2292
  return result;
1992
2293
  }
@@ -1995,7 +2296,8 @@ function usePlanVersions(options) {
1995
2296
  `${versionUrlBase}/${versionId}`,
1996
2297
  { method: "PATCH", body: JSON.stringify(data) }
1997
2298
  );
1998
- if (!result) throw new PlansApiError(0, null, "UpdateDraft returned no body");
2299
+ if (!result)
2300
+ throw markEmptyResponse(new PlansApiError(0, null, "UpdateDraft returned no body"));
1999
2301
  versions.value = versions.value.map((v) => v.id === versionId ? result.planVersion : v);
2000
2302
  return result;
2001
2303
  }
@@ -2004,7 +2306,8 @@ function usePlanVersions(options) {
2004
2306
  `${versionUrlBase}/${versionId}/publish`,
2005
2307
  { method: "POST", body: JSON.stringify(opts) }
2006
2308
  );
2007
- if (!result) throw new PlansApiError(0, null, "Publish returned no body");
2309
+ if (!result)
2310
+ throw markEmptyResponse(new PlansApiError(0, null, "Publish returned no body"));
2008
2311
  versions.value = versions.value.map((v) => v.id === versionId ? result.planVersion : v);
2009
2312
  return result;
2010
2313
  }
@@ -2017,7 +2320,8 @@ function usePlanVersions(options) {
2017
2320
  `${versionUrlBase}/${versionId}/terminate`,
2018
2321
  { method: "POST", body: JSON.stringify({ endsAt }) }
2019
2322
  );
2020
- if (!updated) throw new PlansApiError(0, null, "Terminate returned no body");
2323
+ if (!updated)
2324
+ throw markEmptyResponse(new PlansApiError(0, null, "Terminate returned no body"));
2021
2325
  versions.value = versions.value.map((v) => v.id === versionId ? updated : v);
2022
2326
  return updated;
2023
2327
  }
@@ -2038,7 +2342,7 @@ function usePlanVersions(options) {
2038
2342
  }
2039
2343
 
2040
2344
  // src/vue/use-live-plan-versions.ts
2041
- import { ref as ref21, watch as watch4 } from "vue";
2345
+ import { ref as ref24, watch as watch6 } from "vue";
2042
2346
  function useLivePlanVersions(options) {
2043
2347
  if (!options?.adminEndpoint) {
2044
2348
  throw new Error("useLivePlanVersions: `adminEndpoint` is required.");
@@ -2047,9 +2351,9 @@ function useLivePlanVersions(options) {
2047
2351
  throw new Error("useLivePlanVersions: `plans` is required.");
2048
2352
  }
2049
2353
  const http = options.http ?? defaultHttpClient();
2050
- const livePlanVersions = ref21({});
2051
- const loading = ref21(false);
2052
- const error = ref21(null);
2354
+ const livePlanVersions = ref24({});
2355
+ const loading = ref24(false);
2356
+ const error = ref24(null);
2053
2357
  function authHeaders() {
2054
2358
  const token = options.getAuthToken?.();
2055
2359
  return token ? { Authorization: `Bearer ${token}` } : {};
@@ -2091,7 +2395,7 @@ function useLivePlanVersions(options) {
2091
2395
  loading.value = false;
2092
2396
  }
2093
2397
  }
2094
- watch4(
2398
+ watch6(
2095
2399
  () => options.plans.value.map((p) => p.id).join(","),
2096
2400
  () => {
2097
2401
  if (options.plans.value.length > 0) void refresh();
@@ -2113,12 +2417,12 @@ function findLatestLive(versions) {
2113
2417
  }
2114
2418
 
2115
2419
  // src/vue/use-manifest.ts
2116
- import { ref as ref22 } from "vue";
2420
+ import { ref as ref25 } from "vue";
2117
2421
  function useManifest(options) {
2118
2422
  const loader = new ManifestLoader(options);
2119
- const manifest = ref22(null);
2120
- const loading = ref22(false);
2121
- const error = ref22(null);
2423
+ const manifest = ref25(null);
2424
+ const loading = ref25(false);
2425
+ const error = ref25(null);
2122
2426
  async function load() {
2123
2427
  loading.value = true;
2124
2428
  error.value = null;
@@ -2144,24 +2448,24 @@ function useManifest(options) {
2144
2448
  }
2145
2449
 
2146
2450
  // src/vue/use-nav.ts
2147
- import { computed as computed6 } from "vue";
2451
+ import { computed as computed7 } from "vue";
2148
2452
  function useNav(manifest, options = {}) {
2149
2453
  const { locale } = useSuperAdminI18n();
2150
- const activeLocale = computed6(() => options.locale ?? locale.value);
2151
- const routes = computed6(() => {
2454
+ const activeLocale = computed7(() => options.locale ?? locale.value);
2455
+ const routes = computed7(() => {
2152
2456
  if (!manifest.value) return [];
2153
2457
  return buildRoutes(manifest.value, { ...options, locale: activeLocale.value });
2154
2458
  });
2155
- const sidebar = computed6(
2459
+ const sidebar = computed7(
2156
2460
  () => buildSidebar(routes.value, defaultSectionOrder(activeLocale.value))
2157
2461
  );
2158
2462
  return { routes, sidebar };
2159
2463
  }
2160
2464
 
2161
2465
  // src/vue/use-actions.ts
2162
- import { computed as computed7 } from "vue";
2466
+ import { computed as computed8 } from "vue";
2163
2467
  function useActions(manifest, actions) {
2164
- const registry = computed7(() => {
2468
+ const registry = computed8(() => {
2165
2469
  if (!manifest.value) return null;
2166
2470
  return new ActionRegistry(manifest.value, actions);
2167
2471
  });
@@ -2169,18 +2473,18 @@ function useActions(manifest, actions) {
2169
2473
  }
2170
2474
 
2171
2475
  // src/vue/use-tenant-action-flow.ts
2172
- import { computed as computed8 } from "vue";
2476
+ import { computed as computed9 } from "vue";
2173
2477
  function useTenantActionFlow(manifest, providers = {}) {
2174
2478
  const handlers = useSuperAdminActions();
2175
2479
  const msg = useSaMessages("tenants");
2176
- const registry = computed8(() => {
2480
+ const registry = computed9(() => {
2177
2481
  if (!manifest.value) return null;
2178
2482
  return new ActionRegistry(manifest.value, handlers);
2179
2483
  });
2180
- const orphanedDefs = computed8(
2484
+ const orphanedDefs = computed9(
2181
2485
  () => registry.value ? registry.value.listOrphanedDefs() : []
2182
2486
  );
2183
- const availableActions = computed8(() => {
2487
+ const availableActions = computed9(() => {
2184
2488
  const m = manifest.value;
2185
2489
  const reg = registry.value;
2186
2490
  if (!m || !reg) return [];
@@ -2253,7 +2557,7 @@ async function runFlow(def, row, registry, providers, successTemplate) {
2253
2557
  }
2254
2558
 
2255
2559
  // src/vue/use-platform-tenant-actions.ts
2256
- import { computed as computed9, ref as ref23 } from "vue";
2560
+ import { computed as computed10, ref as ref26 } from "vue";
2257
2561
  var DEFAULT_VISIBLE_FOR_ROW = (def, row) => {
2258
2562
  if (def.actionKey === "tenants.suspend") return row.isActive;
2259
2563
  if (def.actionKey === "tenants.reactivate") return !row.isActive;
@@ -2289,7 +2593,7 @@ function usePlatformTenantActions(options) {
2289
2593
  const iconForActionKey = options.iconForActionKey ?? DEFAULT_ICON_FOR_ACTION_KEY;
2290
2594
  const toneForActionKey = options.toneForActionKey ?? DEFAULT_TONE_FOR_ACTION_KEY;
2291
2595
  const visibleForRow = options.visibleForRow ?? DEFAULT_VISIBLE_FOR_ROW;
2292
- const mfa = ref23({ show: false, description: "", error: "" });
2596
+ const mfa = ref26({ show: false, description: "", error: "" });
2293
2597
  let pendingMfaResolve = null;
2294
2598
  function showMfaDialog(def, ctx) {
2295
2599
  return new Promise((resolve) => {
@@ -2314,7 +2618,7 @@ function usePlatformTenantActions(options) {
2314
2618
  pendingMfaResolve(null);
2315
2619
  }
2316
2620
  }
2317
- const confirmDialog = ref23({
2621
+ const confirmDialog = ref26({
2318
2622
  show: false,
2319
2623
  def: null,
2320
2624
  row: null
@@ -2356,7 +2660,7 @@ function usePlatformTenantActions(options) {
2356
2660
  },
2357
2661
  visibleForRow
2358
2662
  });
2359
- const manifestActions = computed9(
2663
+ const manifestActions = computed10(
2360
2664
  () => flow.availableActions.value.map((def) => ({
2361
2665
  id: def.id,
2362
2666
  label: def.label,
@@ -2370,7 +2674,7 @@ function usePlatformTenantActions(options) {
2370
2674
  }
2371
2675
  }))
2372
2676
  );
2373
- const realOrphans = computed9(() => {
2677
+ const realOrphans = computed10(() => {
2374
2678
  const caps = options.manifest.value?.capabilities ?? {};
2375
2679
  const defs = options.manifest.value?.tenants?.actions ?? [];
2376
2680
  const defByActionKey = new Map(defs.map((d) => [d.actionKey, d]));
@@ -2395,12 +2699,12 @@ function usePlatformTenantActions(options) {
2395
2699
  }
2396
2700
 
2397
2701
  // src/vue/use-batch-columns.ts
2398
- import { ref as ref24, watch as watch5 } from "vue";
2702
+ import { ref as ref27, watch as watch7 } from "vue";
2399
2703
  function useBatchColumns(manifest, tenantIds, options = {}) {
2400
2704
  const fetcher = new BatchColumnFetcher(options);
2401
- const data = ref24({});
2402
- const loading = ref24(false);
2403
- const error = ref24(null);
2705
+ const data = ref27({});
2706
+ const loading = ref27(false);
2707
+ const error = ref27(null);
2404
2708
  async function load() {
2405
2709
  if (!manifest.value || tenantIds.value.length === 0) {
2406
2710
  data.value = {};
@@ -2417,45 +2721,21 @@ function useBatchColumns(manifest, tenantIds, options = {}) {
2417
2721
  loading.value = false;
2418
2722
  }
2419
2723
  }
2420
- watch5([manifest, tenantIds], () => {
2724
+ watch7([manifest, tenantIds], () => {
2421
2725
  void load();
2422
2726
  });
2423
2727
  return { data, loading, error, reload: load };
2424
2728
  }
2425
2729
 
2426
- // src/vue/platform-loaders.ts
2427
- function resolveEndpoints(endpoints) {
2428
- return {
2429
- apiBase: endpoints.apiBase,
2430
- publicBootEndpoint: endpoints.publicBootEndpoint ?? `${endpoints.apiBase}/boot`,
2431
- manifestEndpoint: endpoints.manifestEndpoint ?? `${endpoints.apiBase}/manifest`
2432
- };
2433
- }
2434
- function createPlatformLoaders(options) {
2435
- const resolved = resolveEndpoints(options.endpoints);
2436
- const bootLoader = new BootLoader({
2437
- endpoint: resolved.publicBootEndpoint,
2438
- http: options.http
2439
- });
2440
- const manifestLoader = new ManifestLoader({
2441
- endpoint: resolved.manifestEndpoint,
2442
- http: options.http,
2443
- storage: options.storage,
2444
- storageKeyPrefix: options.storageKeyPrefix,
2445
- getAuthToken: options.getAuthToken
2446
- });
2447
- return { bootLoader, manifestLoader };
2448
- }
2449
-
2450
2730
  // src/vue/manifest-store-factory.ts
2451
2731
  import { defineStore } from "pinia";
2452
- import { ref as ref25 } from "vue";
2732
+ import { ref as ref28 } from "vue";
2453
2733
  function createManifestStore(options) {
2454
2734
  return defineStore(options.id ?? "admin-manifest", () => {
2455
- const manifest = ref25(null);
2456
- const loading = ref25(false);
2457
- const error = ref25(null);
2458
- const loaded = ref25(false);
2735
+ const manifest = ref28(null);
2736
+ const loading = ref28(false);
2737
+ const error = ref28(null);
2738
+ const loaded = ref28(false);
2459
2739
  let inflight = null;
2460
2740
  async function load() {
2461
2741
  loading.value = true;
@@ -2887,6 +3167,7 @@ export {
2887
3167
  ALL_ROWS,
2888
3168
  ActionDefNotInManifestError,
2889
3169
  ActionRegistry,
3170
+ AdminError,
2890
3171
  BatchColumnDriftError,
2891
3172
  BatchColumnFetcher,
2892
3173
  BootLoadError,
@@ -2907,6 +3188,10 @@ export {
2907
3188
  IDENTITY_ACCENT_VALUES,
2908
3189
  IDENTITY_NEUTRAL,
2909
3190
  IDENTITY_NEUTRAL_VALUE,
3191
+ LIST_FIRST_PAGE,
3192
+ LIST_PAGE_SIZE_DEFAULT,
3193
+ LIST_PAGE_SIZE_MAX,
3194
+ LIST_PAGINATION_PARAMS,
2910
3195
  ManifestLoadError,
2911
3196
  ManifestLoader,
2912
3197
  MarketingProjectionsApiError,
@@ -2916,15 +3201,18 @@ export {
2916
3201
  PlansApiError,
2917
3202
  ProjectPageHost,
2918
3203
  PromotionsApiError,
3204
+ SA_COLOR_SCHEMES,
2919
3205
  SA_INTL_LOCALES,
2920
3206
  SA_LOCALES,
2921
3207
  SA_LOCALE_LABELS,
2922
3208
  SA_LOCALE_STORAGE_KEY,
2923
3209
  SA_MESSAGES,
2924
3210
  SA_THEME_KEY,
3211
+ SA_THEME_STORAGE_KEY,
2925
3212
  SERVER_PAGE_SIZE_OPTIONS,
2926
3213
  SUPER_ADMIN_ACTIONS_KEY,
2927
3214
  SUPER_ADMIN_BRAND_KEY,
3215
+ SUPER_ADMIN_CONFIRM_KEY,
2928
3216
  SUPER_ADMIN_ENDPOINTS_KEY,
2929
3217
  SUPER_ADMIN_EXTENSIONS_KEY,
2930
3218
  SUPER_ADMIN_HTTP_KEY,
@@ -2933,19 +3221,28 @@ export {
2933
3221
  SUPER_ADMIN_MANIFEST_CLEAR_CACHE_KEY,
2934
3222
  SUPER_ADMIN_MANIFEST_KEY,
2935
3223
  SUPER_ADMIN_NOTIFY_KEY,
3224
+ SUPER_ADMIN_RESOURCES_KEY,
2936
3225
  TenantSubscriptionBundlesApiError,
3226
+ adminErrorMessage,
3227
+ auditResource,
3228
+ bindResource,
2937
3229
  buildFeatureRegistry,
2938
3230
  buildFeatureRouterGuard,
2939
3231
  buildNavigationGuard,
2940
3232
  buildQuotaRegistry,
2941
3233
  buildRoutes,
2942
3234
  buildSidebar,
3235
+ clampListPage,
3236
+ clampListPageSize,
2943
3237
  countPlans,
2944
3238
  createAdminResourceClient,
2945
3239
  createAdminRoutes,
3240
+ createAxiosHttpClient,
3241
+ createFetchHttpClient,
2946
3242
  createManifestStore,
2947
3243
  createPlatformLoaders,
2948
3244
  createProjectPageHostRoute,
3245
+ createResourceRegistry,
2949
3246
  createSaCatalog,
2950
3247
  createSaTheme,
2951
3248
  createSuperAdminI18n,
@@ -2955,30 +3252,57 @@ export {
2955
3252
  defaultSectionOrder,
2956
3253
  defaultTenantPlanSectionI18n,
2957
3254
  defaultToneForActionKey,
3255
+ defineListOp,
2958
3256
  defineMessages,
3257
+ defineResource,
3258
+ filterQueryString,
2959
3259
  formatCurrency,
2960
3260
  formatMessage,
2961
3261
  getJson,
2962
3262
  identityAccentAt,
2963
3263
  identityAccentFor,
2964
3264
  identityChipStyle,
3265
+ isAdminError,
3266
+ isAxiosNoResponseError,
2965
3267
  isCurrentlyValid,
3268
+ isEmptyResponse,
2966
3269
  isExpired,
2967
3270
  isFutureScheduled,
3271
+ isPlatformError,
2968
3272
  isProductionBoot,
2969
3273
  isSaBuiltinLocale,
3274
+ isSentInQuery,
3275
+ isTransportFailure,
3276
+ listUrl,
3277
+ markEmptyResponse,
3278
+ markPlatformError,
3279
+ markTransportFailure,
2970
3280
  mergeMessages,
2971
3281
  planChangeWizardI18n,
3282
+ planVersionsResource,
3283
+ plansResource,
3284
+ platformResources,
2972
3285
  postJson,
2973
3286
  provideEntitlement,
3287
+ readErrorCode,
3288
+ readErrorDetail,
3289
+ readListPage,
3290
+ requestJson,
3291
+ requestJsonBody,
3292
+ requireServerAnswer,
2974
3293
  resolveExtension,
2975
3294
  resolveLoginBranding,
2976
3295
  resolveMessages,
2977
3296
  resolvePlans,
3297
+ resolveSuperAdminEndpoints,
3298
+ tenantsResource,
3299
+ toAdminError,
2978
3300
  todayIsoDate,
2979
3301
  trimTrailingSlashes,
2980
3302
  useActions,
2981
3303
  useApiList,
3304
+ useAsyncAction,
3305
+ useAsyncData,
2982
3306
  useAuditEntries,
2983
3307
  useBatchColumns,
2984
3308
  useBulkPublish,
@@ -3000,6 +3324,8 @@ export {
3000
3324
  usePlatformTenantActions,
3001
3325
  usePromotions,
3002
3326
  usePublicBoot,
3327
+ useResource,
3328
+ useResourceList,
3003
3329
  useSaMessages,
3004
3330
  useSaTheme,
3005
3331
  useSubscriptionDraft,