@lessly/sdk-app 93.0.0 → 94.1.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.
package/docs/README.md CHANGED
@@ -74,6 +74,7 @@ you need to touch it directly.
74
74
  | `recipes/sdk-usage.md` | You need to call the platform API: creating the SDK client, handling `LesslyApiError`, using the generated `*QueryOptions`/`*MutationOptions` factories. |
75
75
  | `recipes/federation.md` | You're touching the Module Federation setup: shared singletons, the dual-mode build, or the dual-mode CSS graph (the federation vs standalone stylesheets). |
76
76
  | `recipes/access.md` | You're gating UI on the caller's permissions: `app.access`, `useCan`, and rendering a 403 as an empty state rather than an error (APP-012). |
77
+ | `recipes/entitlements.md` | A call can fail with a 402 `entitlement_exhausted` / `entitlement_unavailable` (the organization reached its plan): `isEntitlementError` inside the shell, `upgradeUrl` outside it. |
77
78
  | `recipes/local-dev.md` | You're running the app locally: the dev proxy, device-code login, `.env.local`, or composing your app under a local shell. |
78
79
 
79
80
  ## Keeping this guide current
@@ -0,0 +1,96 @@
1
+ # Recipe: the billing refusal (HTTP 402 `entitlement_exhausted` / `entitlement_unavailable`)
2
+
3
+ Some operations consume something the organization pays for — emails, storage,
4
+ a managed database. Before such an operation the toolkit asks billing whether
5
+ the organization's plan allows it. When it does not, the call fails with
6
+ **HTTP 402** and this body:
7
+
8
+ ```json
9
+ {
10
+ "error": "entitlement_exhausted",
11
+ "reason": "trial_cap_reached",
12
+ "meter": "…",
13
+ "pool": "…",
14
+ "limit": 3000,
15
+ "used": 3000,
16
+ "requested": 1,
17
+ "plan": "trial",
18
+ "upgrade_url": "<Workspace web origin>/organization/billing?upgrade=pro&org=<org_id>"
19
+ }
20
+ ```
21
+
22
+ | Field | Values |
23
+ | --- | --- |
24
+ | `error` | `entitlement_exhausted` (every reason except `trial_class_unavailable`), `entitlement_unavailable` |
25
+ | `reason` | `trial_cap_reached`, `trial_class_unavailable`, `billing_paused`, `budget_paused` |
26
+ | `plan` | `trial`, `pro`, `enterprise`, `none` |
27
+
28
+ This is not your App's error and not the user's mistake: it is the organization
29
+ reaching the edge of its plan. **The Workspace shell shows it** — one window,
30
+ with the reason and a path to Pro — for every federated App and for its own
31
+ calls. Your App renders nothing for it.
32
+
33
+ ---
34
+
35
+ ## 1. Inside the shell: recognise it, render nothing
36
+
37
+ ```tsx
38
+ import { isAccessDenied, isEntitlementError } from '@lepsto/sdk-app';
39
+
40
+ function RequestError({ error }: { error: unknown }) {
41
+ // The shell already opened its refusal window. A second error here is a duplicate.
42
+ if (isEntitlementError(error)) return null;
43
+ if (isAccessDenied(error)) return <EmptyState title="You don't have access to this" />;
44
+ return <ErrorState error={error} />;
45
+ }
46
+ ```
47
+
48
+ Put the branch in the one shared component that classifies errors (see
49
+ `access.md` §3 — state keeps the error *object*, never its message).
50
+ `isEntitlementError` is true for exactly the rule the shell uses: status `402`
51
+ and a body `error` of `entitlement_exhausted` or `entitlement_unavailable`. Any other 402 — a toolkit's
52
+ own payment failure — is false and stays yours to show.
53
+
54
+ Like `isAccessDenied`, it reads `status` and `body` structurally, so an error
55
+ thrown by another copy of this SDK on the page is recognised too.
56
+
57
+ ## 2. Outside the shell: link to the upgrade path
58
+
59
+ A standalone App, or an App calling through its own `fetch`, is invisible to the
60
+ shell. Show a short notice with the link from `upgradeUrl(err)`:
61
+
62
+ ```tsx
63
+ import { upgradeUrl } from '@lepsto/sdk-app';
64
+
65
+ const href = upgradeUrl(error);
66
+ if (href) {
67
+ return (
68
+ <Notice>
69
+ Your organization has reached its plan limit. <a href={href}>Upgrade to Pro</a>
70
+ </Notice>
71
+ );
72
+ }
73
+ ```
74
+
75
+ With your own `fetch`, pass the status and the parsed body:
76
+
77
+ ```ts
78
+ const res = await fetch(url, init);
79
+ if (!res.ok) {
80
+ const err = { status: res.status, body: await res.json().catch(() => null) };
81
+ if (isEntitlementError(err)) showUpgradeNotice(upgradeUrl(err));
82
+ }
83
+ ```
84
+
85
+ `upgradeUrl` returns the URL billing sent, unchanged, and `null` when the error
86
+ is not an entitlement refusal or the URL is not an absolute `http(s)` URL. Never
87
+ build this URL yourself — it names the refused organization.
88
+
89
+ ## What not to do
90
+
91
+ - **Do not retry** the refused call in a loop. Nothing changes until the
92
+ organization upgrades or the next period starts.
93
+ - **Do not cache** the refusal. After an upgrade the next call succeeds.
94
+ - **Do not render it red**, and inside the shell do not render it at all.
95
+ - **Do not branch on `reason`** to decide *whether* to show something — that is
96
+ `isEntitlementError`. `reason`, `used` and `limit` are for wording only.
@@ -71,6 +71,11 @@ try {
71
71
  }
72
72
  ```
73
73
 
74
+ A **402** whose body `error` is `entitlement_exhausted` or
75
+ `entitlement_unavailable` is billing refusing the
76
+ call over the organization's plan. The Workspace shell shows it; see
77
+ `entitlements.md` for `isEntitlementError` and `upgradeUrl`.
78
+
74
79
  ### Missing CSRF cookie
75
80
 
76
81
  Mutating calls (`POST`, `PUT`, `PATCH`, `DELETE`) require the `lepsto_csrf`
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lessly/sdk-app",
3
3
  "description": "The Lessly SDK for App is a TypeScript SDK that provides runtime bindings and type-safe generated code for interacting with Lessly Platform APIs and services. This package generates type definitions and client bindings from API catalogs, enabling developers to build applications that consume Lessly platform services with full type safety and autocompletion.",
4
- "version": "93.0.0",
4
+ "version": "94.1.0",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/lepsto/lepsto-sdk-app.git"