@saasicat/ui-vue 0.25.0 → 0.26.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -8
- package/dist/{catalog-DJcfm5N2.d.cts → catalog-Dch1Ryw0.d.cts} +353 -69
- package/dist/{catalog-DJcfm5N2.d.ts → catalog-Dch1Ryw0.d.ts} +353 -69
- package/dist/{chunk-F7NRM6KI.js → chunk-COVQLDBZ.js} +759 -24
- package/dist/{chunk-TQQMKWN6.js → chunk-DT25QAU2.js} +119 -7
- package/dist/{chunk-D74FFCCP.js → chunk-O3Y3BYYS.js} +96 -178
- package/dist/client/index.cjs +770 -84
- package/dist/client/index.d.cts +429 -9
- package/dist/client/index.d.ts +429 -9
- package/dist/client/index.js +84 -12
- package/dist/index.cjs +1337 -295
- package/dist/index.d.cts +205 -15
- package/dist/index.d.ts +205 -15
- package/dist/index.js +566 -240
- package/dist/quasar/index.cjs +585 -62
- package/dist/quasar/index.d.cts +61 -6
- package/dist/quasar/index.d.ts +61 -6
- package/dist/quasar/index.js +83 -19
- package/dist/{use-sa-theme-OUBlaqgl.d.ts → resource-registry-D7Xjs-5f.d.cts} +157 -7
- package/dist/{use-sa-theme-B5t7oXph.d.cts → resource-registry-TaR7rtq0.d.ts} +157 -7
- package/package.json +6 -4
- package/src/client/admin-error.ts +569 -0
- package/src/client/admin-resource-client.ts +9 -12
- package/src/client/attach-cause.ts +21 -0
- package/src/client/batch-column-fetcher.ts +2 -2
- package/src/client/boot-loader.ts +12 -1
- package/src/client/http/axios-http-client.ts +383 -0
- package/src/client/http/fetch-http-client.ts +89 -0
- package/src/client/http/index.ts +6 -0
- package/src/client/http-json.ts +82 -16
- package/src/client/i18n/messages/discovery.ts +0 -6
- package/src/client/i18n/messages/errors.ts +44 -0
- package/src/client/i18n/messages/marketing.ts +0 -6
- package/src/client/i18n/messages/promos.ts +0 -2
- package/src/client/i18n/messages/shell.ts +12 -0
- package/src/client/i18n/messages.ts +3 -0
- package/src/client/index.ts +6 -0
- package/src/client/manifest-loader.ts +65 -15
- package/src/client/nav-builder.ts +3 -1
- package/src/client/resources/audit.resource.ts +25 -0
- package/src/client/resources/define-resource.ts +166 -0
- package/src/client/resources/index.ts +42 -0
- package/src/client/resources/list-resource.ts +176 -0
- package/src/client/resources/plans.resource.ts +169 -0
- package/src/client/resources/resource-request.ts +117 -0
- package/src/client/resources/tenants.resource.ts +27 -0
- package/src/client/types.ts +10 -3
- package/src/components/BundleVersionPublishDialog.vue +1 -1
- package/src/components/MarketingPromotionsTab.vue +263 -241
- package/src/components/ThemeSwitcher.vue +85 -0
- package/src/components/bundle-editor/BundleCreatePanel.vue +1 -1
- package/src/components/dialogs/PromoCodeDialogFields.vue +20 -20
- package/src/components/plan/PlanCycleToggle.vue +1 -1
- package/src/components/plan-detail/PlanVersionsPanel.vue +5 -2
- package/src/index.ts +5 -0
- package/src/pages-standard/AdminLayout.vue +57 -0
- package/src/pages-standard/AdminManifestErrorPage.vue +4 -3
- package/src/pages-standard/DiscoveryPage.vue +8 -1
- package/src/pages-standard/MarketingCatalogPage.vue +14 -3
- package/src/pages-standard/SuperAdminLoginPage.vue +23 -2
- package/src/pages-standard/SuperAdminSetupWizard.vue +44 -7
- package/src/pages-standard/discovery-page/DiscoveryFeatureCard.vue +7 -2
- package/src/pages-standard/marketing-catalog/MarketingCatalogAdmin.vue +44 -6
- package/src/pages-standard/marketing-catalog/MarketingCatalogPreview.vue +8 -2
- package/src/pages-tenant/PackageSnapshotPanel.vue +31 -2
- package/src/quasar/confirm.ts +83 -0
- package/src/quasar/create-super-admin-app.ts +84 -10
- package/src/quasar/dark-bridge.ts +47 -10
- package/src/quasar/index.ts +1 -0
- package/src/vue/create-admin-routes.ts +9 -1
- package/src/vue/platform-loaders.ts +12 -2
- package/src/vue/resource-registry.ts +211 -0
- package/src/vue/super-admin-context.ts +20 -5
- package/src/vue/ui-confirm.ts +76 -0
- package/src/vue/use-api-list.ts +50 -45
- package/src/vue/use-async-action.ts +170 -0
- package/src/vue/use-async-data.ts +83 -0
- package/src/vue/use-bundles.ts +34 -7
- package/src/vue/use-catalog-entries.ts +37 -12
- package/src/vue/use-discovery.ts +27 -6
- package/src/vue/use-marketing-projections.ts +29 -7
- package/src/vue/use-plans.ts +36 -8
- package/src/vue/use-promotions.ts +27 -7
- package/src/vue/use-resource-list.ts +272 -0
- package/src/vue/use-sa-theme.ts +34 -5
- package/src/vue/use-tenant-subscription-bundles.ts +22 -3
package/README.md
CHANGED
|
@@ -15,12 +15,35 @@ The package is layered so that each entry only loads what it names.
|
|
|
15
15
|
Lower layers never import upward; ESLint (`no-restricted-imports`, repo
|
|
16
16
|
root config) enforces the boundaries in CI.
|
|
17
17
|
|
|
18
|
-
| Entry | Source | May import | Contents
|
|
19
|
-
| ----------------------------------------------------------------------- | -------------------------------- | ---------------------------------------- |
|
|
20
|
-
| `@saasicat/ui-vue/client` | `src/client/` | `@saasicat/types` only | Framework-free core: `BootLoader`, `ManifestLoader` (ETag cache), nav builder, action registry, batch column fetcher, `HttpClient`/`KvStore` contract
|
|
21
|
-
| `@saasicat/ui-vue` (main) | `src/vue/` (+ client re-exports) | Vue, `vue-router`, Pinia — **no Quasar** | Composables (`useTenants`, `usePlanEditor`, …), router guards, injection keys + shell contract, notify-port type, optional Pinia store factory.
|
|
22
|
-
| `@saasicat/ui-vue/quasar` | `src/quasar/` | everything above + Quasar | `createSuperAdminApp()` bootstrap and the Quasar notify-port implementation.
|
|
23
|
-
| `@saasicat/ui-vue/pages-standard/*`, `/pages-tenant/*`, `/components/*` | SFC directories | everything | The Quasar reference UI, shipped as raw `.vue` from `src/` (compiled by the consumer's Vite).
|
|
18
|
+
| Entry | Source | May import | Contents |
|
|
19
|
+
| ----------------------------------------------------------------------- | -------------------------------- | ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
20
|
+
| `@saasicat/ui-vue/client` | `src/client/` | `@saasicat/types` only | Framework-free core: `BootLoader`, `ManifestLoader` (ETag cache), nav builder, action registry, batch column fetcher, the `HttpClient`/`KvStore` contract with its `fetch` and axios adapters, `AdminError`. Usable from any framework or plain Node/TypeScript. |
|
|
21
|
+
| `@saasicat/ui-vue` (main) | `src/vue/` (+ client re-exports) | Vue, `vue-router`, Pinia — **no Quasar** | Composables (`useTenants`, `usePlanEditor`, …), router guards, injection keys + shell contract, notify-port type, optional Pinia store factory. |
|
|
22
|
+
| `@saasicat/ui-vue/quasar` | `src/quasar/` | everything above + Quasar | `createSuperAdminApp()` bootstrap and the Quasar notify-port implementation. |
|
|
23
|
+
| `@saasicat/ui-vue/pages-standard/*`, `/pages-tenant/*`, `/components/*` | SFC directories | everything | The Quasar reference UI, shipped as raw `.vue` from `src/` (compiled by the consumer's Vite). |
|
|
24
|
+
|
|
25
|
+
### The shipped source has a language floor
|
|
26
|
+
|
|
27
|
+
The last row is not a build output: those subpaths hand out `.vue` and `.ts`
|
|
28
|
+
straight from `src/`, so **your** `tsconfig` compiles them, not ours. Anything
|
|
29
|
+
they reach — which includes `src/client/` and `src/vue/` — therefore has to
|
|
30
|
+
compile under the oldest language level a consumer uses.
|
|
31
|
+
|
|
32
|
+
That floor is **ES2021** (`lib: ES2021, DOM`), with **TypeScript 5.0** as the
|
|
33
|
+
minimum compiler. The language level is checked rather than promised:
|
|
34
|
+
`pnpm --filter @saasicat/ui-vue test:shipped-source` compiles the closure
|
|
35
|
+
reachable from every source-shipping subpath at that level, with
|
|
36
|
+
`isolatedModules`, `useDefineForClassFields` and `strictPropertyInitialization`
|
|
37
|
+
set the way a Vite app sets them, and CI runs it. The directories come from the export map, so a new source subpath
|
|
38
|
+
is covered the day it is added. The compiler version is not checked — `satisfies`
|
|
39
|
+
in the shipped source needs 4.9 or newer whatever `lib` says, and testing that
|
|
40
|
+
would mean installing old compilers.
|
|
41
|
+
|
|
42
|
+
Practically this rules out a handful of ES2022 conveniences in shipped code —
|
|
43
|
+
`new Error(msg, { cause })` and `Object.hasOwn` are the two that have come up.
|
|
44
|
+
Use `attachCause()` from `src/client/attach-cause.ts` and
|
|
45
|
+
`Object.prototype.hasOwnProperty.call()` instead. Code that only ever runs from
|
|
46
|
+
`dist/` is unaffected.
|
|
24
47
|
|
|
25
48
|
Rules of thumb when contributing:
|
|
26
49
|
|
|
@@ -48,8 +71,32 @@ createSuperAdminApp({
|
|
|
48
71
|
});
|
|
49
72
|
```
|
|
50
73
|
|
|
51
|
-
Confirm
|
|
52
|
-
|
|
74
|
+
### Confirm port
|
|
75
|
+
|
|
76
|
+
A second seam, `UiConfirm` (`SUPER_ADMIN_CONFIRM_KEY`), works the same way:
|
|
77
|
+
`createSuperAdminApp()` provides a Quasar `Dialog` default, and an app with its
|
|
78
|
+
own modal system replaces it.
|
|
79
|
+
|
|
80
|
+
It is available to your own pages today. The standard pages do **not** use it
|
|
81
|
+
yet — `UsersPage`, `PilotsPage`, `PromoCodesPage` and `PlatformEmailPage` still
|
|
82
|
+
call `q.dialog()` directly, so replacing the port does not change what they show.
|
|
83
|
+
Moving them onto it belongs to the page migration.
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
createSuperAdminApp({
|
|
87
|
+
// ...
|
|
88
|
+
confirm: (request) => myModals.ask(request),
|
|
89
|
+
});
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
A request may ask for a value as well as a yes — an audit reason before a
|
|
93
|
+
password reset, the date a pilot is extended to — so the port resolves
|
|
94
|
+
`{ ok, value? }` rather than a bare boolean. An implementation must actually
|
|
95
|
+
ask: one that resolves `{ ok: true }` outright turns every guarded action into
|
|
96
|
+
an unguarded one.
|
|
97
|
+
|
|
98
|
+
Dialogs that collect a form, and the one that displays a generated one-time
|
|
99
|
+
password, are not confirmations and stay as they are.
|
|
53
100
|
|
|
54
101
|
## Usage
|
|
55
102
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as _saasicat_types from '@saasicat/types';
|
|
2
|
+
import { AuditQuery, AuditEntry, TenantListFilter, TenantDto, PlanVersionRow, CreatePlanVersionDraftData, PlanVersionMutationResult, UpdatePlanVersionDraftData, PlanRow, CreatePlanData, UpdatePlanData, StandardPageKey, AdminManifest, TenantActionDef } from '@saasicat/types';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Minimal abstraction over `fetch`. Consumers may pass their own
|
|
@@ -33,11 +34,280 @@ interface KvStore {
|
|
|
33
34
|
*/
|
|
34
35
|
declare function defaultKvStore(): KvStore;
|
|
35
36
|
/**
|
|
36
|
-
* Default `HttpClient` over `fetch
|
|
37
|
-
*
|
|
37
|
+
* Default `HttpClient` over `fetch`, with no base URL and no headers of its
|
|
38
|
+
* own. Consumers pass their own variant when they need auth headers, tenant
|
|
39
|
+
* headers or retry logic — `createFetchHttpClient({ headers })` and
|
|
40
|
+
* `createAxiosHttpClient(instance)` are the two the package ships.
|
|
41
|
+
*
|
|
42
|
+
* It delegates rather than calling `fetch` itself so that there is one
|
|
43
|
+
* implementation to reason about instead of two that drift.
|
|
38
44
|
*/
|
|
39
45
|
declare function defaultHttpClient(): HttpClient;
|
|
40
46
|
|
|
47
|
+
/**
|
|
48
|
+
* What an operation needs to know about the app it runs in, beyond its own
|
|
49
|
+
* arguments.
|
|
50
|
+
*
|
|
51
|
+
* Today each composable takes these as its own options and each consumer
|
|
52
|
+
* passes them again per page. They are app-wide constants — which is why they
|
|
53
|
+
* belong in one context rather than in every signature.
|
|
54
|
+
*/
|
|
55
|
+
interface ResourceContext {
|
|
56
|
+
/** Admin API root, e.g. `/api/v1/admin`. No trailing slash. */
|
|
57
|
+
readonly apiBase: string;
|
|
58
|
+
/** The project whose catalogue is being administered. */
|
|
59
|
+
readonly projectKey: string;
|
|
60
|
+
/** Active UI locale, for the operations whose payload is per-language. */
|
|
61
|
+
readonly locale: string;
|
|
62
|
+
}
|
|
63
|
+
/** One operation: everything it needs, in the order it needs it. */
|
|
64
|
+
type ResourceOp<A extends unknown[], R> = (http: HttpClient, ctx: ResourceContext, ...args: A) => Promise<R>;
|
|
65
|
+
/** The operations of one resource, keyed by name. */
|
|
66
|
+
type ResourceOps = Record<string, ResourceOp<never[], unknown>>;
|
|
67
|
+
interface ResourceDef<TOps extends ResourceOps> {
|
|
68
|
+
/** Name this resource is registered and overridden under. */
|
|
69
|
+
readonly key: string;
|
|
70
|
+
readonly ops: TOps;
|
|
71
|
+
/**
|
|
72
|
+
* Whether these operations read `ctx.projectKey`.
|
|
73
|
+
*
|
|
74
|
+
* Declared per descriptor rather than known centrally, because it is not a
|
|
75
|
+
* property of the registry: one of the four platform resources is
|
|
76
|
+
* project-scoped and three are not, and the tenant list would be refused
|
|
77
|
+
* for a project it never asks about. `bindResource` refuses to bind a
|
|
78
|
+
* descriptor that declares this against a context that names no project.
|
|
79
|
+
*
|
|
80
|
+
* `resources-declare-their-project-scope.test.js` drives every operation
|
|
81
|
+
* and fails when a declaration and what the operations actually read
|
|
82
|
+
* disagree — in either direction.
|
|
83
|
+
*/
|
|
84
|
+
readonly projectScoped: boolean;
|
|
85
|
+
}
|
|
86
|
+
/** What a descriptor declares beyond its name and its operations. */
|
|
87
|
+
interface DefineResourceOptions {
|
|
88
|
+
/** See `ResourceDef.projectScoped`. Default `false`. */
|
|
89
|
+
projectScoped?: boolean;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* The same operations with `(http, ctx)` already supplied — the form a page or
|
|
93
|
+
* a composable calls.
|
|
94
|
+
*/
|
|
95
|
+
type Bound<TOps extends ResourceOps> = {
|
|
96
|
+
[K in keyof TOps]: TOps[K] extends ResourceOp<infer A, infer R> ? (...args: A) => Promise<R> : never;
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* Declares a resource.
|
|
100
|
+
*
|
|
101
|
+
* Deliberately thin — it exists to name the set and to fix the type, not to
|
|
102
|
+
* add behaviour. Anything it did at definition time would be a thing an
|
|
103
|
+
* override could not replace.
|
|
104
|
+
*/
|
|
105
|
+
declare function defineResource<const TOps extends ResourceOps>(key: string, ops: TOps, options?: DefineResourceOptions): ResourceDef<TOps>;
|
|
106
|
+
/**
|
|
107
|
+
* Supplies `(http, ctx)` once and hands back the callable operations.
|
|
108
|
+
*
|
|
109
|
+
* The context is read at call time rather than captured per operation, so a
|
|
110
|
+
* context that changes — a locale the operator switches, a project the shell
|
|
111
|
+
* re-scopes — is picked up by calls made afterwards without rebinding.
|
|
112
|
+
*/
|
|
113
|
+
declare function bindResource<TOps extends ResourceOps>(def: ResourceDef<TOps>, http: HttpClient, ctx: ResourceContext | (() => ResourceContext)): Bound<TOps>;
|
|
114
|
+
|
|
115
|
+
/** 1-based: the page a list starts on, and the one a changed filter returns to. */
|
|
116
|
+
declare const LIST_FIRST_PAGE = 1;
|
|
117
|
+
/**
|
|
118
|
+
* What the admin API falls back to when `pageSize` is absent. Sent explicitly
|
|
119
|
+
* rather than relied on, so the URL states what it asked for.
|
|
120
|
+
*/
|
|
121
|
+
declare const LIST_PAGE_SIZE_DEFAULT = 50;
|
|
122
|
+
/**
|
|
123
|
+
* The largest page the admin API serves. There is no "all" to pass on, which is
|
|
124
|
+
* why `SERVER_PAGE_SIZE_OPTIONS` leaves Quasar's `ALL_ROWS` out as well.
|
|
125
|
+
*/
|
|
126
|
+
declare const LIST_PAGE_SIZE_MAX = 200;
|
|
127
|
+
/** The two parameters the pagination owns; a filter may not carry them. */
|
|
128
|
+
declare const LIST_PAGINATION_PARAMS: readonly ["page", "pageSize"];
|
|
129
|
+
/**
|
|
130
|
+
* A filter without the two keys the pagination owns.
|
|
131
|
+
*
|
|
132
|
+
* The platform's filter types (`TenantListFilter`, `AuditQuery`) declare
|
|
133
|
+
* `page` and `pageSize` because they were once passed as one object. Serialised
|
|
134
|
+
* after the pagination they silently overwrite it — `goToPage(3)` moves the ref
|
|
135
|
+
* while the request asks for page 7 — so a descriptor takes the filter without
|
|
136
|
+
* them and the page number travels in its own field.
|
|
137
|
+
*/
|
|
138
|
+
type ListFilterOf<TFilter> = Omit<TFilter, 'page' | 'pageSize'>;
|
|
139
|
+
/** Everything one list request varies: which page, how big, filtered by what. */
|
|
140
|
+
interface ListQuery<TFilter extends object = Record<string, unknown>> {
|
|
141
|
+
/** 1-based. Defaults to `LIST_FIRST_PAGE`. */
|
|
142
|
+
page?: number;
|
|
143
|
+
/** Defaults to `LIST_PAGE_SIZE_DEFAULT`. */
|
|
144
|
+
pageSize?: number;
|
|
145
|
+
/** Serialised as `?key=value`; see `isSentInQuery` for what is left out. */
|
|
146
|
+
filter?: TFilter;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* One page of a list, as the admin API answers it.
|
|
150
|
+
*
|
|
151
|
+
* `total` is optional because it is genuinely absent from some answers: a
|
|
152
|
+
* controller returning a bare array has reported the rows it sent and nothing
|
|
153
|
+
* about the rest, and an envelope may omit the field altogether. A number
|
|
154
|
+
* invented here would read as "rows matching the filter" under a paginator that
|
|
155
|
+
* then offers pages nobody can reach.
|
|
156
|
+
*/
|
|
157
|
+
interface ResourceListPage<T> {
|
|
158
|
+
items: T[];
|
|
159
|
+
/** Rows matching the filter — only when the server said so. */
|
|
160
|
+
total?: number;
|
|
161
|
+
/** What the server echoed back about the page it served, when it echoed anything. */
|
|
162
|
+
page?: number;
|
|
163
|
+
pageSize?: number;
|
|
164
|
+
}
|
|
165
|
+
/** A list operation: the query in, one page out. */
|
|
166
|
+
type ListOp<TRow, TFilter extends object = Record<string, unknown>> = ResourceOp<[
|
|
167
|
+
query?: ListQuery<TFilter>
|
|
168
|
+
], ResourceListPage<TRow>>;
|
|
169
|
+
/**
|
|
170
|
+
* Whether a filter value reaches the query string at all.
|
|
171
|
+
*
|
|
172
|
+
* `undefined`, `null` and `''` are the three spellings of "not filtered" that
|
|
173
|
+
* a form produces, and sending them would filter on the literal text `null`.
|
|
174
|
+
* Everything else goes as `String(value)` — including `0` and `false`, which
|
|
175
|
+
* are answers, not absences.
|
|
176
|
+
*/
|
|
177
|
+
declare function isSentInQuery(value: unknown): boolean;
|
|
178
|
+
/**
|
|
179
|
+
* `?a=b` for a filter on its own, or `''` when nothing survives — the form the
|
|
180
|
+
* admin endpoints that do not page take their filter in.
|
|
181
|
+
*/
|
|
182
|
+
declare function filterQueryString(filter: object): string;
|
|
183
|
+
/**
|
|
184
|
+
* The URL one page of a list is requested from.
|
|
185
|
+
*
|
|
186
|
+
* `page` and `pageSize` are always present and always first — an endpoint that
|
|
187
|
+
* ignores them is free to, but the request says which page it meant. The filter
|
|
188
|
+
* follows in its own insertion order, and the endpoint may already carry a
|
|
189
|
+
* query of its own.
|
|
190
|
+
*/
|
|
191
|
+
declare function listUrl<TFilter extends object>(endpoint: string, query?: ListQuery<TFilter>): string;
|
|
192
|
+
/**
|
|
193
|
+
* Reads whatever a list endpoint answered into one page.
|
|
194
|
+
*
|
|
195
|
+
* Both shapes are real: some controllers return a bare array, others the
|
|
196
|
+
* `{ items, page, pageSize, total }` envelope. Accepting only the envelope is
|
|
197
|
+
* what once rendered three tenants as an empty table (see
|
|
198
|
+
* `use-api-list-shape.test.js`). What the answer did not state stays absent
|
|
199
|
+
* rather than being filled in — the caller decides what "no total" means for
|
|
200
|
+
* its paginator.
|
|
201
|
+
*/
|
|
202
|
+
declare function readListPage<T>(raw: unknown): ResourceListPage<T>;
|
|
203
|
+
/** Keeps a requested page on the 1-based scale the API counts on. */
|
|
204
|
+
declare function clampListPage(page: number): number;
|
|
205
|
+
/** Keeps a requested page size inside the 1..`LIST_PAGE_SIZE_MAX` the API serves. */
|
|
206
|
+
declare function clampListPageSize(size: number): number;
|
|
207
|
+
/**
|
|
208
|
+
* A list operation over one endpoint: the second of a pair, and the reason it
|
|
209
|
+
* is a function rather than two hand-written descriptors.
|
|
210
|
+
*
|
|
211
|
+
* The endpoint is a function of the context so a `context` override on the
|
|
212
|
+
* registry — an app pointing one resource at a legacy base — reaches it without
|
|
213
|
+
* the descriptor knowing.
|
|
214
|
+
*/
|
|
215
|
+
declare function defineListOp<TRow, TFilter extends object = Record<string, unknown>>(endpoint: (ctx: ResourceContext) => string): ListOp<TRow, TFilter>;
|
|
216
|
+
|
|
217
|
+
/** What the audit list can be narrowed by. The page number is not a filter. */
|
|
218
|
+
type AuditListFilter = ListFilterOf<AuditQuery>;
|
|
219
|
+
declare const auditResource: ResourceDef<{
|
|
220
|
+
readonly list: ListOp<AuditEntry, AuditListFilter>;
|
|
221
|
+
}>;
|
|
222
|
+
|
|
223
|
+
/** What the tenants list can be narrowed by. The page number is not a filter. */
|
|
224
|
+
type TenantsListFilter = ListFilterOf<TenantListFilter>;
|
|
225
|
+
declare const tenantsResource: ResourceDef<{
|
|
226
|
+
readonly list: ListOp<TenantDto, TenantsListFilter>;
|
|
227
|
+
}>;
|
|
228
|
+
|
|
229
|
+
/** What `publish` may override at the moment a version goes live. */
|
|
230
|
+
interface PublishPlanVersionOptions {
|
|
231
|
+
forceRegressive?: boolean;
|
|
232
|
+
allowZeroPrice?: boolean;
|
|
233
|
+
validFrom?: string | null;
|
|
234
|
+
validUntil?: string | null;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* The plan catalogue — the one platform resource that is read per project.
|
|
238
|
+
*
|
|
239
|
+
* `list`, `tenantCounts` and `create` all carry `ctx.projectKey`, so binding
|
|
240
|
+
* this against a context that names no project is refused rather than sent (see
|
|
241
|
+
* `bindResource`). Its three siblings address a plan or a version by id and are
|
|
242
|
+
* not declared here.
|
|
243
|
+
*/
|
|
244
|
+
declare const plansResource: ResourceDef<{
|
|
245
|
+
readonly list: (http: HttpClient, ctx: ResourceContext) => Promise<PlanRow[]>;
|
|
246
|
+
readonly tenantCounts: (http: HttpClient, ctx: ResourceContext) => Promise<Record<string, number>>;
|
|
247
|
+
/**
|
|
248
|
+
* The project comes from the context, not from the caller.
|
|
249
|
+
*
|
|
250
|
+
* The endpoint takes it in the body, and the composable this replaces
|
|
251
|
+
* made that the caller's job — so a page had to keep hold of the
|
|
252
|
+
* project it was administering just to create in it, and `list()` and
|
|
253
|
+
* `create()` read two different sources for one value. A stale one
|
|
254
|
+
* creates a plan outside the project the list is showing.
|
|
255
|
+
*/
|
|
256
|
+
readonly create: (http: HttpClient, ctx: ResourceContext, data: Omit<CreatePlanData, "projectKey">) => Promise<PlanRow>;
|
|
257
|
+
readonly update: (http: HttpClient, ctx: ResourceContext, planId: string, data: UpdatePlanData) => Promise<PlanRow>;
|
|
258
|
+
/** Marks the plan deleted. Fails while it still has versions. */
|
|
259
|
+
readonly softDelete: (http: HttpClient, ctx: ResourceContext, planId: string) => Promise<void>;
|
|
260
|
+
/** Removes the plan for good. 422 `PLAN_HAS_VERSIONS` when it cannot. */
|
|
261
|
+
readonly hardDelete: (http: HttpClient, ctx: ResourceContext, planId: string) => Promise<void>;
|
|
262
|
+
}>;
|
|
263
|
+
declare const planVersionsResource: ResourceDef<{
|
|
264
|
+
readonly listForPlan: (http: HttpClient, ctx: ResourceContext, planId: string) => Promise<PlanVersionRow[]>;
|
|
265
|
+
readonly createDraft: (http: HttpClient, ctx: ResourceContext, planId: string, data: Omit<CreatePlanVersionDraftData, "planId">) => Promise<PlanVersionMutationResult>;
|
|
266
|
+
readonly updateDraft: (http: HttpClient, ctx: ResourceContext, versionId: string, data: UpdatePlanVersionDraftData) => Promise<PlanVersionMutationResult>;
|
|
267
|
+
readonly publish: (http: HttpClient, ctx: ResourceContext, versionId: string, options?: PublishPlanVersionOptions) => Promise<PlanVersionMutationResult>;
|
|
268
|
+
readonly discardDraft: (http: HttpClient, ctx: ResourceContext, versionId: string) => Promise<void>;
|
|
269
|
+
/**
|
|
270
|
+
* Ends a live version on `endsAt`, with no successor. Idempotent — a
|
|
271
|
+
* second call with another date overwrites the first.
|
|
272
|
+
*
|
|
273
|
+
* `endsAt` is the date itself, not a payload: the endpoint's body is
|
|
274
|
+
* `{ endsAt }` and wrapping it is this operation's job, not the caller's.
|
|
275
|
+
*/
|
|
276
|
+
readonly terminate: (http: HttpClient, ctx: ResourceContext, versionId: string, endsAt: string) => Promise<PlanVersionRow>;
|
|
277
|
+
}>;
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Every resource the shell offers by default.
|
|
281
|
+
*
|
|
282
|
+
* The registry binds these; an app overrides by key. Partial while the roster
|
|
283
|
+
* grows — a page whose resource is not here yet keeps its props.
|
|
284
|
+
*/
|
|
285
|
+
declare const platformResources: {
|
|
286
|
+
plans: ResourceDef<{
|
|
287
|
+
readonly list: (http: HttpClient, ctx: ResourceContext) => Promise<_saasicat_types.PlanRow[]>;
|
|
288
|
+
readonly tenantCounts: (http: HttpClient, ctx: ResourceContext) => Promise<Record<string, number>>;
|
|
289
|
+
readonly create: (http: HttpClient, ctx: ResourceContext, data: Omit<_saasicat_types.CreatePlanData, "projectKey">) => Promise<_saasicat_types.PlanRow>;
|
|
290
|
+
readonly update: (http: HttpClient, ctx: ResourceContext, planId: string, data: _saasicat_types.UpdatePlanData) => Promise<_saasicat_types.PlanRow>;
|
|
291
|
+
readonly softDelete: (http: HttpClient, ctx: ResourceContext, planId: string) => Promise<void>;
|
|
292
|
+
readonly hardDelete: (http: HttpClient, ctx: ResourceContext, planId: string) => Promise<void>;
|
|
293
|
+
}>;
|
|
294
|
+
planVersions: ResourceDef<{
|
|
295
|
+
readonly listForPlan: (http: HttpClient, ctx: ResourceContext, planId: string) => Promise<_saasicat_types.PlanVersionRow[]>;
|
|
296
|
+
readonly createDraft: (http: HttpClient, ctx: ResourceContext, planId: string, data: Omit<_saasicat_types.CreatePlanVersionDraftData, "planId">) => Promise<_saasicat_types.PlanVersionMutationResult>;
|
|
297
|
+
readonly updateDraft: (http: HttpClient, ctx: ResourceContext, versionId: string, data: _saasicat_types.UpdatePlanVersionDraftData) => Promise<_saasicat_types.PlanVersionMutationResult>;
|
|
298
|
+
readonly publish: (http: HttpClient, ctx: ResourceContext, versionId: string, options?: PublishPlanVersionOptions) => Promise<_saasicat_types.PlanVersionMutationResult>;
|
|
299
|
+
readonly discardDraft: (http: HttpClient, ctx: ResourceContext, versionId: string) => Promise<void>;
|
|
300
|
+
readonly terminate: (http: HttpClient, ctx: ResourceContext, versionId: string, endsAt: string) => Promise<_saasicat_types.PlanVersionRow>;
|
|
301
|
+
}>;
|
|
302
|
+
tenants: ResourceDef<{
|
|
303
|
+
readonly list: ListOp<_saasicat_types.TenantDto, TenantsListFilter>;
|
|
304
|
+
}>;
|
|
305
|
+
audit: ResourceDef<{
|
|
306
|
+
readonly list: ListOp<_saasicat_types.AuditEntry, AuditListFilter>;
|
|
307
|
+
}>;
|
|
308
|
+
};
|
|
309
|
+
type PlatformResources = typeof platformResources;
|
|
310
|
+
|
|
41
311
|
/** Locales the platform itself ships a catalog for. */
|
|
42
312
|
type SaBuiltinLocale = 'de' | 'en';
|
|
43
313
|
/**
|
|
@@ -60,64 +330,6 @@ declare const SA_LOCALE_LABELS: Record<SaBuiltinLocale, string>;
|
|
|
60
330
|
/** Narrows untrusted input to a locale the platform ships a catalog for. */
|
|
61
331
|
declare function isSaBuiltinLocale(value: unknown): value is SaBuiltinLocale;
|
|
62
332
|
|
|
63
|
-
type SaNavMessages = {
|
|
64
|
-
readonly pages: Record<StandardPageKey, string>;
|
|
65
|
-
readonly sections: {
|
|
66
|
-
readonly overview: string;
|
|
67
|
-
readonly catalog: string;
|
|
68
|
-
readonly customers: string;
|
|
69
|
-
readonly system: string;
|
|
70
|
-
};
|
|
71
|
-
};
|
|
72
|
-
declare const navMessages: Record<SaBuiltinLocale, SaNavMessages>;
|
|
73
|
-
|
|
74
|
-
/** Consumer implementation; receives the action inputs as a generic object. */
|
|
75
|
-
type ActionHandler<TInput = unknown, TResult = unknown> = (input: TInput) => Promise<TResult>;
|
|
76
|
-
interface ResolvedAction<TInput = unknown, TResult = unknown> {
|
|
77
|
-
def: TenantActionDef;
|
|
78
|
-
handler: ActionHandler<TInput, TResult>;
|
|
79
|
-
}
|
|
80
|
-
declare class MissingHandlerError extends Error {
|
|
81
|
-
constructor(actionKey: string);
|
|
82
|
-
}
|
|
83
|
-
declare class ActionDefNotInManifestError extends Error {
|
|
84
|
-
constructor(actionKey: string);
|
|
85
|
-
}
|
|
86
|
-
declare class ActionRegistry {
|
|
87
|
-
private readonly defs;
|
|
88
|
-
private readonly handlers;
|
|
89
|
-
constructor(manifest: AdminManifest, handlers?: Record<string, ActionHandler>);
|
|
90
|
-
/**
|
|
91
|
-
* Registers a handler after the fact (e.g. when consumer code loads
|
|
92
|
-
* lazily). Throws `ActionDefNotInManifestError` if the `actionKey` is not
|
|
93
|
-
* declared in the manifest — prevents dead registrations.
|
|
94
|
-
*/
|
|
95
|
-
register<TInput, TResult>(actionKey: string, handler: ActionHandler<TInput, TResult>): void;
|
|
96
|
-
/**
|
|
97
|
-
* Returns the `{def, handler}` pair. Throws if the key is missing from
|
|
98
|
-
* the manifest or no handler is registered. The shell's UI layer calls
|
|
99
|
-
* the method, checks `def.requiresMfa` / `def.confirmType` for the
|
|
100
|
-
* pre-flow, and then calls `handler(input)`.
|
|
101
|
-
*/
|
|
102
|
-
get<TInput = unknown, TResult = unknown>(actionKey: string): ResolvedAction<TInput, TResult>;
|
|
103
|
-
/**
|
|
104
|
-
* Convenience: `get(key).handler(input)`. UI convenience for actions
|
|
105
|
-
* that need neither MFA nor confirm.
|
|
106
|
-
*/
|
|
107
|
-
dispatch<TInput, TResult>(actionKey: string, input: TInput): Promise<TResult>;
|
|
108
|
-
/**
|
|
109
|
-
* List of actionKeys that are declared in the manifest but have no
|
|
110
|
-
* handler. Consumers use this in a doctor check to detect drift between
|
|
111
|
-
* the manifest and the shell build.
|
|
112
|
-
*/
|
|
113
|
-
listOrphanedDefs(): string[];
|
|
114
|
-
/**
|
|
115
|
-
* List of registered handlers that are missing from the manifest. Drift
|
|
116
|
-
* in the other direction.
|
|
117
|
-
*/
|
|
118
|
-
listOrphanedHandlers(): string[];
|
|
119
|
-
}
|
|
120
|
-
|
|
121
333
|
/** Nested string map — the shape of every message namespace. */
|
|
122
334
|
interface MessageTree {
|
|
123
335
|
readonly [key: string]: string | MessageTree;
|
|
@@ -478,9 +690,6 @@ declare const discoveryMessages: Record<SaBuiltinLocale, {
|
|
|
478
690
|
replacedBy: string;
|
|
479
691
|
replaces: string;
|
|
480
692
|
reapproveEmphasis: string;
|
|
481
|
-
errorDiscoveryHttp: string;
|
|
482
|
-
errorRescanHttp: string;
|
|
483
|
-
errorCatalogHttp: string;
|
|
484
693
|
capList: {
|
|
485
694
|
orphanFeature: string;
|
|
486
695
|
removedFromCode: string;
|
|
@@ -622,6 +831,20 @@ declare const emailMessages: Record<SaBuiltinLocale, {
|
|
|
622
831
|
};
|
|
623
832
|
}>;
|
|
624
833
|
|
|
834
|
+
declare const errorsMessages: Record<SaBuiltinLocale, {
|
|
835
|
+
network: string;
|
|
836
|
+
emptyResponse: string;
|
|
837
|
+
unauthorized: string;
|
|
838
|
+
forbidden: string;
|
|
839
|
+
notFound: string;
|
|
840
|
+
conflict: string;
|
|
841
|
+
validation: string;
|
|
842
|
+
rateLimited: string;
|
|
843
|
+
server: string;
|
|
844
|
+
httpStatus: string;
|
|
845
|
+
unexpected: string;
|
|
846
|
+
}>;
|
|
847
|
+
|
|
625
848
|
declare const marketingMessages: Record<SaBuiltinLocale, {
|
|
626
849
|
topFeatures: string;
|
|
627
850
|
tabs: {
|
|
@@ -745,11 +968,19 @@ declare const marketingMessages: Record<SaBuiltinLocale, {
|
|
|
745
968
|
delete: string;
|
|
746
969
|
deleteConfirm: string;
|
|
747
970
|
};
|
|
748
|
-
errors: {
|
|
749
|
-
projectionsApi: string;
|
|
750
|
-
};
|
|
751
971
|
}>;
|
|
752
972
|
|
|
973
|
+
type SaNavMessages = {
|
|
974
|
+
readonly pages: Record<StandardPageKey, string>;
|
|
975
|
+
readonly sections: {
|
|
976
|
+
readonly overview: string;
|
|
977
|
+
readonly catalog: string;
|
|
978
|
+
readonly customers: string;
|
|
979
|
+
readonly system: string;
|
|
980
|
+
};
|
|
981
|
+
};
|
|
982
|
+
declare const navMessages: Record<SaBuiltinLocale, SaNavMessages>;
|
|
983
|
+
|
|
753
984
|
declare const pilotsMessages: Record<SaBuiltinLocale, {
|
|
754
985
|
title: string;
|
|
755
986
|
subtitleCount: string;
|
|
@@ -1334,7 +1565,6 @@ declare const plansMessages: Record<SaBuiltinLocale, {
|
|
|
1334
1565
|
declare const promosMessages: Record<SaBuiltinLocale, {
|
|
1335
1566
|
title: string;
|
|
1336
1567
|
createAction: string;
|
|
1337
|
-
apiErrorHttpStatus: string;
|
|
1338
1568
|
list: {
|
|
1339
1569
|
searchLabel: string;
|
|
1340
1570
|
tileScheduled: string;
|
|
@@ -1440,6 +1670,12 @@ declare const shellMessages: Record<SaBuiltinLocale, {
|
|
|
1440
1670
|
roleBadge: string;
|
|
1441
1671
|
logout: string;
|
|
1442
1672
|
language: string;
|
|
1673
|
+
theme: {
|
|
1674
|
+
label: string;
|
|
1675
|
+
light: string;
|
|
1676
|
+
dark: string;
|
|
1677
|
+
system: string;
|
|
1678
|
+
};
|
|
1443
1679
|
};
|
|
1444
1680
|
drawer: {
|
|
1445
1681
|
docs: string;
|
|
@@ -1589,6 +1825,7 @@ declare const usersMessages: Record<SaBuiltinLocale, {
|
|
|
1589
1825
|
/** Complete platform catalog for one locale, keyed by namespace. */
|
|
1590
1826
|
type SaMessages = {
|
|
1591
1827
|
readonly common: (typeof commonMessages)['de'];
|
|
1828
|
+
readonly errors: (typeof errorsMessages)['de'];
|
|
1592
1829
|
readonly nav: (typeof navMessages)['de'];
|
|
1593
1830
|
readonly shell: (typeof shellMessages)['de'];
|
|
1594
1831
|
readonly dashboard: (typeof dashboardMessages)['de'];
|
|
@@ -1616,6 +1853,53 @@ declare const SA_MESSAGES: Record<SaBuiltinLocale, SaMessages>;
|
|
|
1616
1853
|
*/
|
|
1617
1854
|
declare function resolveMessages(locale: SaBuiltinLocale, overrides?: SaMessagesOverrides): SaMessages;
|
|
1618
1855
|
|
|
1856
|
+
/** Consumer implementation; receives the action inputs as a generic object. */
|
|
1857
|
+
type ActionHandler<TInput = unknown, TResult = unknown> = (input: TInput) => Promise<TResult>;
|
|
1858
|
+
interface ResolvedAction<TInput = unknown, TResult = unknown> {
|
|
1859
|
+
def: TenantActionDef;
|
|
1860
|
+
handler: ActionHandler<TInput, TResult>;
|
|
1861
|
+
}
|
|
1862
|
+
declare class MissingHandlerError extends Error {
|
|
1863
|
+
constructor(actionKey: string);
|
|
1864
|
+
}
|
|
1865
|
+
declare class ActionDefNotInManifestError extends Error {
|
|
1866
|
+
constructor(actionKey: string);
|
|
1867
|
+
}
|
|
1868
|
+
declare class ActionRegistry {
|
|
1869
|
+
private readonly defs;
|
|
1870
|
+
private readonly handlers;
|
|
1871
|
+
constructor(manifest: AdminManifest, handlers?: Record<string, ActionHandler>);
|
|
1872
|
+
/**
|
|
1873
|
+
* Registers a handler after the fact (e.g. when consumer code loads
|
|
1874
|
+
* lazily). Throws `ActionDefNotInManifestError` if the `actionKey` is not
|
|
1875
|
+
* declared in the manifest — prevents dead registrations.
|
|
1876
|
+
*/
|
|
1877
|
+
register<TInput, TResult>(actionKey: string, handler: ActionHandler<TInput, TResult>): void;
|
|
1878
|
+
/**
|
|
1879
|
+
* Returns the `{def, handler}` pair. Throws if the key is missing from
|
|
1880
|
+
* the manifest or no handler is registered. The shell's UI layer calls
|
|
1881
|
+
* the method, checks `def.requiresMfa` / `def.confirmType` for the
|
|
1882
|
+
* pre-flow, and then calls `handler(input)`.
|
|
1883
|
+
*/
|
|
1884
|
+
get<TInput = unknown, TResult = unknown>(actionKey: string): ResolvedAction<TInput, TResult>;
|
|
1885
|
+
/**
|
|
1886
|
+
* Convenience: `get(key).handler(input)`. UI convenience for actions
|
|
1887
|
+
* that need neither MFA nor confirm.
|
|
1888
|
+
*/
|
|
1889
|
+
dispatch<TInput, TResult>(actionKey: string, input: TInput): Promise<TResult>;
|
|
1890
|
+
/**
|
|
1891
|
+
* List of actionKeys that are declared in the manifest but have no
|
|
1892
|
+
* handler. Consumers use this in a doctor check to detect drift between
|
|
1893
|
+
* the manifest and the shell build.
|
|
1894
|
+
*/
|
|
1895
|
+
listOrphanedDefs(): string[];
|
|
1896
|
+
/**
|
|
1897
|
+
* List of registered handlers that are missing from the manifest. Drift
|
|
1898
|
+
* in the other direction.
|
|
1899
|
+
*/
|
|
1900
|
+
listOrphanedHandlers(): string[];
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1619
1903
|
/** A language an app brings along, on top of the two the platform ships. */
|
|
1620
1904
|
interface SaLocaleDefinition {
|
|
1621
1905
|
/**
|
|
@@ -1672,4 +1956,4 @@ interface SaCatalog {
|
|
|
1672
1956
|
*/
|
|
1673
1957
|
declare function createSaCatalog(options?: SaCatalogOptions): SaCatalog;
|
|
1674
1958
|
|
|
1675
|
-
export { type ActionHandler as A, DEFAULT_SA_LOCALE as D, type HttpClient as H, type KvStore as K, type MessageTree as M, type
|
|
1959
|
+
export { filterQueryString as $, type ActionHandler as A, type Bound as B, SA_LOCALE_LABELS as C, DEFAULT_SA_LOCALE as D, SA_MESSAGES as E, type SaBuiltinLocale as F, type SaCatalog as G, type HttpClient as H, type SaCatalogOptions as I, type TranslationOf as J, type KvStore as K, LIST_FIRST_PAGE as L, type MessageTree as M, auditResource as N, bindResource as O, type PlatformResources as P, clampListPage as Q, type ResourceDef as R, type SaLocale as S, type TenantsListFilter as T, clampListPageSize as U, createSaCatalog as V, defaultHttpClient as W, defaultKvStore as X, defineListOp as Y, defineMessages as Z, defineResource as _, type SaMessages as a, isSaBuiltinLocale as a0, isSentInQuery as a1, listUrl as a2, mergeMessages as a3, planVersionsResource as a4, plansResource as a5, readListPage as a6, resolveMessages as a7, tenantsResource as a8, type SaNavMessages as a9, type SaLocaleOption as b, type SaLocaleDefinition as c, type SaMessagesOverrides as d, type ResourceOps as e, type ResourceContext as f, type ResourceListPage as g, ActionRegistry as h, ActionDefNotInManifestError as i, type AuditListFilter as j, type DefineResourceOptions as k, type HttpResponse as l, LIST_PAGE_SIZE_DEFAULT as m, LIST_PAGE_SIZE_MAX as n, LIST_PAGINATION_PARAMS as o, platformResources as p, type ListFilterOf as q, type ListOp as r, type ListQuery as s, MissingHandlerError as t, type PartialMessages as u, type PublishPlanVersionOptions as v, type ResolvedAction as w, type ResourceOp as x, SA_INTL_LOCALES as y, SA_LOCALES as z };
|