@loopwise/admin-sdk 0.1.0-beta.4 → 0.1.0-beta.5
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/CHANGELOG.md +79 -22
- package/README.md +29 -9
- package/dist/better-auth.d.mts +5 -7
- package/dist/better-auth.mjs +7 -7
- package/dist/index.d.mts +9 -3
- package/dist/index.mjs +13 -5
- package/package.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,13 +1,63 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Changelog
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
All notable changes to `@loopwise/admin-sdk` are documented here.
|
|
4
|
+
|
|
5
|
+
Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
6
|
+
Versioning: [SemVer](https://semver.org/) with pre-1.0 semantics — minor bumps
|
|
7
|
+
(`0.x → 0.x+1`) may include breaking changes; beta tags (`0.1.0-beta.N`)
|
|
8
|
+
follow the same rules as a single 0.x line.
|
|
9
|
+
|
|
10
|
+
## [Unreleased]
|
|
11
|
+
|
|
12
|
+
## [0.1.0-beta.5] — 2026-10-28
|
|
13
|
+
|
|
14
|
+
Moves the `/better-auth` integration to better-auth 1.7, which registers
|
|
15
|
+
`genericOAuth` providers as core social providers
|
|
16
|
+
([better-auth#9069](https://github.com/better-auth/better-auth/pull/9069)).
|
|
17
|
+
better-auth 1.6 has no fix for
|
|
18
|
+
[GHSA-965c-763c-88jm](https://github.com/better-auth/better-auth/security/advisories/GHSA-965c-763c-88jm)
|
|
19
|
+
(fixed in 1.7.7), so 1.6 support is dropped. See
|
|
20
|
+
[TEACH-25069](https://linear.app/kaik/issue/TEACH-25069).
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- **`LoopwiseError#retryAfter`** — on a `RATE_LIMIT` error, the seconds from
|
|
25
|
+
the upstream `Retry-After` header (undefined when the header is absent or
|
|
26
|
+
not an integer). The Admin API now sends it both for request-count limits
|
|
27
|
+
and for the concurrency / execution-time limits introduced in
|
|
28
|
+
[TEACH-22231](https://linear.app/kaik/issue/TEACH-22231).
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
|
|
32
|
+
- **Breaking:** the optional `better-auth` peer dependency is now
|
|
33
|
+
`>=1.7.7` (was `>=1.6.11`). Upgrade `better-auth` to 1.7.7 or later.
|
|
34
|
+
- **Breaking:** `getLoopwiseRedirectURI()` returns
|
|
35
|
+
`<baseURL>/api/auth/callback/<providerId>` (was
|
|
36
|
+
`<baseURL>/api/auth/oauth2/callback/<providerId>`), the only callback
|
|
37
|
+
route better-auth 1.7 serves. Add the new URI to your OAuth client's
|
|
38
|
+
Redirect URIs at `/oauth/applications` before deploying, and remove the
|
|
39
|
+
old one once the upgrade is live.
|
|
40
|
+
- **Breaking (client):** start sign-in with
|
|
41
|
+
`authClient.signIn.social({ provider: 'loopwise' })`; better-auth 1.7
|
|
42
|
+
removed `signIn.oauth2`. On the server, `auth.api.getAccessToken()` now
|
|
43
|
+
selects the account by `accountId` rather than `providerId` — the README
|
|
44
|
+
shows the `listUserAccounts()` lookup.
|
|
45
|
+
|
|
46
|
+
### Fixed
|
|
47
|
+
|
|
48
|
+
- **Accounts are keyed by the OIDC `sub` claim** whatever the discovery
|
|
49
|
+
document advertises. Without this, better-auth 1.7 reads the profile's
|
|
50
|
+
`id` field when discovery lists no id_token signing algorithm; Loopwise
|
|
51
|
+
userinfo has no `id`, so sign-in fails.
|
|
52
|
+
|
|
53
|
+
## [0.1.0-beta.4] — 2026-06-01
|
|
4
54
|
|
|
5
55
|
Docs-only release — surface the OIDC extension claims shipped in
|
|
6
56
|
[TEACH-18913](https://linear.app/kaik/issue/TEACH-18913) on the
|
|
7
57
|
npm-rendered README so integrators see the canonical `mapProfileToUser`
|
|
8
58
|
pattern without leaving the package page. No code change.
|
|
9
59
|
|
|
10
|
-
###
|
|
60
|
+
### Documentation
|
|
11
61
|
|
|
12
62
|
- **README `mapProfileToUser` example** — replaces the
|
|
13
63
|
`// pending TEACH-18913` placeholder with the shipped pattern that
|
|
@@ -26,7 +76,7 @@ pattern without leaving the package page. No code change.
|
|
|
26
76
|
See [TEACH-18914](https://linear.app/kaik/issue/TEACH-18914) for the
|
|
27
77
|
docs PR this release surfaces.
|
|
28
78
|
|
|
29
|
-
## 0.1.0-beta.3
|
|
79
|
+
## [0.1.0-beta.3] — 2026-05-22
|
|
30
80
|
|
|
31
81
|
DX quick wins informed by the first real integration ([nii-course-system](https://github.com/niischool-tw/nii-course-system/pull/38),
|
|
32
82
|
agent-driven, shipped against `0.1.0-beta.0`). See
|
|
@@ -34,17 +84,7 @@ agent-driven, shipped against `0.1.0-beta.0`). See
|
|
|
34
84
|
feedback report; [TEACH-18912](https://linear.app/kaik/issue/TEACH-18912)
|
|
35
85
|
for this PR.
|
|
36
86
|
|
|
37
|
-
###
|
|
38
|
-
|
|
39
|
-
- **`baseUrl` → `baseURL`** on both `LoopwiseAuthOptions` (better-auth
|
|
40
|
-
wrapper) and `LoopwiseAdminConfig` (core admin client). Casing now
|
|
41
|
-
matches better-auth's own `baseURL` / `BETTER_AUTH_URL` convention,
|
|
42
|
-
removing a same-module typo footgun. Migration is a one-character
|
|
43
|
-
rename at any call site that passes the option explicitly; TS
|
|
44
|
-
catches it at compile time. Callers relying on the default are
|
|
45
|
-
unaffected.
|
|
46
|
-
|
|
47
|
-
### Features
|
|
87
|
+
### Added
|
|
48
88
|
|
|
49
89
|
- **`mapProfileToUser` pass-through on `loopwise({})`** — populate
|
|
50
90
|
custom `User` columns at sign-up (e.g. `teachifyUserId: profile.sub`)
|
|
@@ -55,8 +95,15 @@ for this PR.
|
|
|
55
95
|
URI to whitelist on your OAuth client — the better-auth callback
|
|
56
96
|
path includes a `/oauth2/` segment that's easy to miss otherwise.
|
|
57
97
|
|
|
58
|
-
###
|
|
98
|
+
### Changed
|
|
59
99
|
|
|
100
|
+
- **Breaking:** `baseUrl` → `baseURL` on both `LoopwiseAuthOptions`
|
|
101
|
+
(better-auth wrapper) and `LoopwiseAdminConfig` (core admin client).
|
|
102
|
+
Casing now matches better-auth's own `baseURL` / `BETTER_AUTH_URL`
|
|
103
|
+
convention, removing a same-module typo footgun. Migration is a
|
|
104
|
+
one-character rename at any call site that passes the option
|
|
105
|
+
explicitly; TS catches it at compile time. Callers relying on the
|
|
106
|
+
default are unaffected.
|
|
60
107
|
- **`org_admin` default scope shrunk to `['openid', 'profile', 'email']`**
|
|
61
108
|
(was `['openid', 'profile', 'email', 'courses:read', 'members:read']`).
|
|
62
109
|
New OAuth clients don't have API scopes enabled by default, so the
|
|
@@ -65,34 +112,36 @@ for this PR.
|
|
|
65
112
|
`scopes` explicitly AND enable matching scopes on their OAuth client
|
|
66
113
|
in the Loopwise admin UI.
|
|
67
114
|
|
|
68
|
-
###
|
|
115
|
+
### Documentation
|
|
69
116
|
|
|
70
117
|
- README adds Redirect URI, Scopes, Profile-mapping, and Roadmap
|
|
71
118
|
sections informed by the same feedback report.
|
|
72
119
|
|
|
73
|
-
## 0.1.0-beta.2
|
|
120
|
+
## [0.1.0-beta.2] — 2026-05-21
|
|
74
121
|
|
|
75
|
-
|
|
122
|
+
### Added
|
|
76
123
|
|
|
77
124
|
- `admin.members.list({ perPage, page, role })` returns `AdminMemberPage`
|
|
78
|
-
(mirrors `courses.list` shape).
|
|
125
|
+
(mirrors `courses.list` shape) — second typed resource.
|
|
79
126
|
- `AdminMember`: `id`, `name`, `email`, `phoneNumber`, `createdAt`,
|
|
80
127
|
`lastSignInAt`.
|
|
81
128
|
- `AdminMemberRole`: `'student' | 'teaching_assistant'` mirrors the
|
|
82
129
|
schema enum.
|
|
83
130
|
- Requires OAuth scope `members:read`.
|
|
84
131
|
|
|
85
|
-
## 0.1.0-beta.1
|
|
132
|
+
## [0.1.0-beta.1] — 2026-05-21
|
|
86
133
|
|
|
87
134
|
No functional changes. Validates the tag-triggered OIDC publish path
|
|
88
135
|
now that the npm Trusted Publisher entry is configured (the first
|
|
89
136
|
publish — `0.1.0-beta.0` — had to be done manually because npm OIDC
|
|
90
137
|
can't create new package names).
|
|
91
138
|
|
|
92
|
-
## 0.1.0-beta.0
|
|
139
|
+
## [0.1.0-beta.0] — 2026-05-21
|
|
93
140
|
|
|
94
141
|
First public pre-release.
|
|
95
142
|
|
|
143
|
+
### Added
|
|
144
|
+
|
|
96
145
|
- `createAdminClient({ accessToken, refreshAccessToken?, baseUrl? })` returns a
|
|
97
146
|
`LoopwiseAdmin` interface with `.graphql<TData, TVars>()` typed pass-through
|
|
98
147
|
against `/admin/graphql`.
|
|
@@ -107,3 +156,11 @@ First public pre-release.
|
|
|
107
156
|
Server Metadata discovery URL (`/.well-known/oauth-authorization-server`),
|
|
108
157
|
audience-aware providerId (`loopwise` / `loopwise-member`) and scope
|
|
109
158
|
defaults.
|
|
159
|
+
|
|
160
|
+
[Unreleased]: https://github.com/kaikhq/manekineko/compare/@loopwise/admin-sdk@0.1.0-beta.5...HEAD
|
|
161
|
+
[0.1.0-beta.5]: https://github.com/kaikhq/manekineko/compare/@loopwise/admin-sdk@0.1.0-beta.4...@loopwise/admin-sdk@0.1.0-beta.5
|
|
162
|
+
[0.1.0-beta.4]: https://github.com/kaikhq/manekineko/compare/@loopwise/admin-sdk@0.1.0-beta.3...@loopwise/admin-sdk@0.1.0-beta.4
|
|
163
|
+
[0.1.0-beta.3]: https://github.com/kaikhq/manekineko/compare/@loopwise/admin-sdk@0.1.0-beta.2...@loopwise/admin-sdk@0.1.0-beta.3
|
|
164
|
+
[0.1.0-beta.2]: https://github.com/kaikhq/manekineko/compare/@loopwise/admin-sdk@0.1.0-beta.1...@loopwise/admin-sdk@0.1.0-beta.2
|
|
165
|
+
[0.1.0-beta.1]: https://github.com/kaikhq/manekineko/compare/@loopwise/admin-sdk@0.1.0-beta.0...@loopwise/admin-sdk@0.1.0-beta.1
|
|
166
|
+
[0.1.0-beta.0]: https://github.com/kaikhq/manekineko/releases/tag/@loopwise/admin-sdk@0.1.0-beta.0
|
package/README.md
CHANGED
|
@@ -20,6 +20,8 @@ const { courses } = await admin.graphql<{ courses: { nodes: { id: string; name:
|
|
|
20
20
|
|
|
21
21
|
## With better-auth (Next.js)
|
|
22
22
|
|
|
23
|
+
Requires `better-auth` 1.7.7 or later.
|
|
24
|
+
|
|
23
25
|
```ts
|
|
24
26
|
// auth.ts
|
|
25
27
|
import { betterAuth } from 'better-auth';
|
|
@@ -43,9 +45,14 @@ import { createAdminClient } from '@loopwise/admin-sdk';
|
|
|
43
45
|
import { auth } from '@/lib/auth';
|
|
44
46
|
|
|
45
47
|
export default async function Page() {
|
|
48
|
+
const requestHeaders = await headers();
|
|
49
|
+
const accounts = await auth.api.listUserAccounts({ headers: requestHeaders });
|
|
50
|
+
const loopwiseAccount = accounts.find((a) => a.providerId === 'loopwise');
|
|
51
|
+
if (!loopwiseAccount) throw new Error('Not signed in with Loopwise');
|
|
52
|
+
|
|
46
53
|
const { accessToken } = await auth.api.getAccessToken({
|
|
47
|
-
body: {
|
|
48
|
-
headers:
|
|
54
|
+
body: { accountId: loopwiseAccount.id },
|
|
55
|
+
headers: requestHeaders,
|
|
49
56
|
});
|
|
50
57
|
const admin = createAdminClient({ accessToken });
|
|
51
58
|
const { courses } = await admin.graphql<{ courses: { nodes: { id: string; name: string }[] } }>({
|
|
@@ -57,10 +64,8 @@ export default async function Page() {
|
|
|
57
64
|
|
|
58
65
|
### Redirect URI
|
|
59
66
|
|
|
60
|
-
Better Auth's catch-all handler is mounted at `/api/auth/[...all]`,
|
|
61
|
-
|
|
62
|
-
note the `oauth2/` segment (different from many OAuth tutorials that show
|
|
63
|
-
`/api/auth/callback/...`).
|
|
67
|
+
Better Auth's catch-all handler is mounted at `/api/auth/[...all]`, and
|
|
68
|
+
the Loopwise provider's callback is `/api/auth/callback/<providerId>`.
|
|
64
69
|
|
|
65
70
|
Use the exported helper to compute the exact URI to whitelist on your
|
|
66
71
|
OAuth client:
|
|
@@ -71,12 +76,27 @@ import { getLoopwiseRedirectURI } from '@loopwise/admin-sdk/better-auth';
|
|
|
71
76
|
console.log(
|
|
72
77
|
getLoopwiseRedirectURI({ baseURL: process.env.BETTER_AUTH_URL! }),
|
|
73
78
|
);
|
|
74
|
-
// dev: http://localhost:3000/api/auth/
|
|
75
|
-
// prod: https://your.app/api/auth/
|
|
79
|
+
// dev: http://localhost:3000/api/auth/callback/loopwise
|
|
80
|
+
// prod: https://your.app/api/auth/callback/loopwise
|
|
76
81
|
```
|
|
77
82
|
|
|
78
83
|
For multi-audience apps, pass `audience: 'member'` to get
|
|
79
|
-
`/
|
|
84
|
+
`/api/auth/callback/loopwise-member`.
|
|
85
|
+
|
|
86
|
+
### Signing in from the client
|
|
87
|
+
|
|
88
|
+
The Loopwise provider is a regular better-auth social provider, so the
|
|
89
|
+
client starts the flow with `signIn.social`:
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
import { createAuthClient } from 'better-auth/react';
|
|
93
|
+
|
|
94
|
+
const authClient = createAuthClient();
|
|
95
|
+
|
|
96
|
+
await authClient.signIn.social({ provider: 'loopwise', callbackURL: '/courses' });
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Use `provider: 'loopwise-member'` for the member audience.
|
|
80
100
|
|
|
81
101
|
### Scopes
|
|
82
102
|
|
package/dist/better-auth.d.mts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { BetterAuthPlugin } from "better-auth/types";
|
|
2
|
-
|
|
3
2
|
//#region src/better-auth.d.ts
|
|
4
|
-
type LoopwiseAuthAudience =
|
|
3
|
+
type LoopwiseAuthAudience = "org_admin" | "member";
|
|
5
4
|
interface LoopwiseAuthOptions {
|
|
6
5
|
/** OAuth client ID issued at `/oauth/applications` in the school admin UI. */
|
|
7
6
|
clientId: string;
|
|
@@ -81,21 +80,20 @@ interface GetRedirectURIOptions {
|
|
|
81
80
|
}
|
|
82
81
|
/**
|
|
83
82
|
* Compute the exact redirect URI you need to register on your Loopwise
|
|
84
|
-
* OAuth client
|
|
85
|
-
* `/api/auth/
|
|
86
|
-
* (different from many OAuth tutorials that show `/api/auth/callback/...`).
|
|
83
|
+
* OAuth client: better-auth's callback route,
|
|
84
|
+
* `/api/auth/callback/<providerId>`, under your app's `baseURL`.
|
|
87
85
|
*
|
|
88
86
|
* @example
|
|
89
87
|
* // dev
|
|
90
88
|
* getLoopwiseRedirectURI({ baseURL: 'http://localhost:3000' });
|
|
91
|
-
* // → 'http://localhost:3000/api/auth/
|
|
89
|
+
* // → 'http://localhost:3000/api/auth/callback/loopwise'
|
|
92
90
|
*
|
|
93
91
|
* // prod, member audience
|
|
94
92
|
* getLoopwiseRedirectURI({
|
|
95
93
|
* baseURL: 'https://app.example.com',
|
|
96
94
|
* audience: 'member',
|
|
97
95
|
* });
|
|
98
|
-
* // → 'https://app.example.com/api/auth/
|
|
96
|
+
* // → 'https://app.example.com/api/auth/callback/loopwise-member'
|
|
99
97
|
*
|
|
100
98
|
* Use this at app boot to print the URI so you (or your agent) can paste
|
|
101
99
|
* it straight into the OAuth client's redirect-URI list.
|
package/dist/better-auth.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { genericOAuth } from "better-auth/plugins";
|
|
2
2
|
//#region src/better-auth.ts
|
|
3
3
|
const DEFAULT_BASE_URL = "https://app.loopwise.com";
|
|
4
|
-
const
|
|
4
|
+
const BETTER_AUTH_CALLBACK_PATH = "/api/auth/callback";
|
|
5
5
|
const AUDIENCE_PROFILES = {
|
|
6
6
|
org_admin: {
|
|
7
7
|
providerId: "loopwise",
|
|
@@ -22,28 +22,27 @@ const AUDIENCE_PROFILES = {
|
|
|
22
22
|
};
|
|
23
23
|
/**
|
|
24
24
|
* Compute the exact redirect URI you need to register on your Loopwise
|
|
25
|
-
* OAuth client
|
|
26
|
-
* `/api/auth/
|
|
27
|
-
* (different from many OAuth tutorials that show `/api/auth/callback/...`).
|
|
25
|
+
* OAuth client: better-auth's callback route,
|
|
26
|
+
* `/api/auth/callback/<providerId>`, under your app's `baseURL`.
|
|
28
27
|
*
|
|
29
28
|
* @example
|
|
30
29
|
* // dev
|
|
31
30
|
* getLoopwiseRedirectURI({ baseURL: 'http://localhost:3000' });
|
|
32
|
-
* // → 'http://localhost:3000/api/auth/
|
|
31
|
+
* // → 'http://localhost:3000/api/auth/callback/loopwise'
|
|
33
32
|
*
|
|
34
33
|
* // prod, member audience
|
|
35
34
|
* getLoopwiseRedirectURI({
|
|
36
35
|
* baseURL: 'https://app.example.com',
|
|
37
36
|
* audience: 'member',
|
|
38
37
|
* });
|
|
39
|
-
* // → 'https://app.example.com/api/auth/
|
|
38
|
+
* // → 'https://app.example.com/api/auth/callback/loopwise-member'
|
|
40
39
|
*
|
|
41
40
|
* Use this at app boot to print the URI so you (or your agent) can paste
|
|
42
41
|
* it straight into the OAuth client's redirect-URI list.
|
|
43
42
|
*/
|
|
44
43
|
function getLoopwiseRedirectURI(options) {
|
|
45
44
|
const profile = AUDIENCE_PROFILES[options.audience ?? "org_admin"];
|
|
46
|
-
return `${options.baseURL.replace(/\/$/, "")}${
|
|
45
|
+
return `${options.baseURL.replace(/\/$/, "")}${BETTER_AUTH_CALLBACK_PATH}/${profile.providerId}`;
|
|
47
46
|
}
|
|
48
47
|
function loopwise(options) {
|
|
49
48
|
const profile = AUDIENCE_PROFILES[options.audience ?? "org_admin"];
|
|
@@ -56,6 +55,7 @@ function loopwise(options) {
|
|
|
56
55
|
pkce: true,
|
|
57
56
|
accessType: "offline",
|
|
58
57
|
scopes: options.scopes ?? [...profile.defaultScopes],
|
|
58
|
+
accountSubject: ({ profile: userInfo }) => userInfo.sub ?? "",
|
|
59
59
|
mapProfileToUser: options.mapProfileToUser
|
|
60
60
|
}] });
|
|
61
61
|
}
|
package/dist/index.d.mts
CHANGED
|
@@ -22,8 +22,11 @@ interface LoopwiseAdminConfig {
|
|
|
22
22
|
*
|
|
23
23
|
* Typical wiring (with better-auth):
|
|
24
24
|
* refreshAccessToken: async () => {
|
|
25
|
+
* const accounts = await auth.api.listUserAccounts({ headers: req.headers });
|
|
26
|
+
* const account = accounts.find((a) => a.providerId === 'loopwise');
|
|
27
|
+
* if (!account) throw new Error('Not signed in with Loopwise');
|
|
25
28
|
* const { accessToken } = await auth.api.getAccessToken({
|
|
26
|
-
* body: {
|
|
29
|
+
* body: { accountId: account.id }, headers: req.headers,
|
|
27
30
|
* });
|
|
28
31
|
* return accessToken;
|
|
29
32
|
* }
|
|
@@ -79,7 +82,7 @@ declare class LoopwiseAdminClient {
|
|
|
79
82
|
}
|
|
80
83
|
//#endregion
|
|
81
84
|
//#region src/error.d.ts
|
|
82
|
-
type LoopwiseErrorCode =
|
|
85
|
+
type LoopwiseErrorCode = "NETWORK" | "AUTH" | "GRAPHQL" | "RATE_LIMIT" | "CONFIG";
|
|
83
86
|
interface GraphQLError {
|
|
84
87
|
message: string;
|
|
85
88
|
path?: (string | number)[];
|
|
@@ -91,11 +94,14 @@ declare class LoopwiseError extends Error {
|
|
|
91
94
|
readonly status?: number;
|
|
92
95
|
readonly graphqlErrors?: GraphQLError[];
|
|
93
96
|
readonly requestId?: string;
|
|
97
|
+
/** Seconds to wait before retrying, from the upstream `Retry-After`; only on `RATE_LIMIT`. */
|
|
98
|
+
readonly retryAfter?: number;
|
|
94
99
|
constructor(code: LoopwiseErrorCode, message: string, detail?: {
|
|
95
100
|
endpoint?: string;
|
|
96
101
|
status?: number;
|
|
97
102
|
graphqlErrors?: GraphQLError[];
|
|
98
103
|
requestId?: string;
|
|
104
|
+
retryAfter?: number;
|
|
99
105
|
});
|
|
100
106
|
}
|
|
101
107
|
//#endregion
|
|
@@ -165,7 +171,7 @@ interface AdminMemberPage {
|
|
|
165
171
|
hasNextPage: boolean;
|
|
166
172
|
hasPreviousPage: boolean;
|
|
167
173
|
}
|
|
168
|
-
type AdminMemberRole =
|
|
174
|
+
type AdminMemberRole = "student" | "teaching_assistant";
|
|
169
175
|
interface MembersListArgs {
|
|
170
176
|
/** Items per page. Schema default 20, max 50. */
|
|
171
177
|
perPage?: number;
|
package/dist/index.mjs
CHANGED
|
@@ -5,6 +5,8 @@ var LoopwiseError = class extends Error {
|
|
|
5
5
|
status;
|
|
6
6
|
graphqlErrors;
|
|
7
7
|
requestId;
|
|
8
|
+
/** Seconds to wait before retrying, from the upstream `Retry-After`; only on `RATE_LIMIT`. */
|
|
9
|
+
retryAfter;
|
|
8
10
|
constructor(code, message, detail = {}) {
|
|
9
11
|
super(message);
|
|
10
12
|
this.name = "LoopwiseError";
|
|
@@ -13,6 +15,7 @@ var LoopwiseError = class extends Error {
|
|
|
13
15
|
this.status = detail.status;
|
|
14
16
|
this.graphqlErrors = detail.graphqlErrors;
|
|
15
17
|
this.requestId = detail.requestId;
|
|
18
|
+
this.retryAfter = detail.retryAfter;
|
|
16
19
|
}
|
|
17
20
|
};
|
|
18
21
|
//#endregion
|
|
@@ -110,11 +113,16 @@ var LoopwiseAdminClient = class {
|
|
|
110
113
|
status: 401,
|
|
111
114
|
requestId
|
|
112
115
|
});
|
|
113
|
-
if (response.status === 429)
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
116
|
+
if (response.status === 429) {
|
|
117
|
+
const retryAfterHeader = response.headers.get("retry-after");
|
|
118
|
+
const retryAfter = retryAfterHeader !== null && /^\d+$/.test(retryAfterHeader) ? Number(retryAfterHeader) : void 0;
|
|
119
|
+
throw new LoopwiseError("RATE_LIMIT", retryAfter === void 0 ? "Rate limit exceeded" : `Rate limit exceeded; retry after ${retryAfter}s`, {
|
|
120
|
+
endpoint: this.endpoint,
|
|
121
|
+
status: 429,
|
|
122
|
+
requestId,
|
|
123
|
+
retryAfter
|
|
124
|
+
});
|
|
125
|
+
}
|
|
118
126
|
if (!response.ok) throw new LoopwiseError("NETWORK", `Upstream returned HTTP ${response.status}`, {
|
|
119
127
|
endpoint: this.endpoint,
|
|
120
128
|
status: response.status,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loopwise/admin-sdk",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.5",
|
|
4
4
|
"description": "Loopwise admin GraphQL SDK — typed access to /admin/graphql for tenant-built applications",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"test": "vitest run"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
|
-
"better-auth": ">=1.
|
|
50
|
+
"better-auth": ">=1.7.7"
|
|
51
51
|
},
|
|
52
52
|
"peerDependenciesMeta": {
|
|
53
53
|
"better-auth": {
|
|
@@ -55,11 +55,11 @@
|
|
|
55
55
|
}
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
|
-
"better-auth": "1.
|
|
58
|
+
"better-auth": "1.7.14",
|
|
59
59
|
"tsconfig": "workspace:*",
|
|
60
|
-
"tsdown": "
|
|
61
|
-
"typescript": "
|
|
62
|
-
"vitest": "
|
|
60
|
+
"tsdown": "catalog:",
|
|
61
|
+
"typescript": "catalog:typescript7",
|
|
62
|
+
"vitest": "catalog:"
|
|
63
63
|
},
|
|
64
64
|
"publishConfig": {
|
|
65
65
|
"access": "public"
|