@pikku/kysely 0.13.27 → 0.13.28
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
CHANGED
|
@@ -1,3 +1,39 @@
|
|
|
1
|
+
## 0.13.28
|
|
2
|
+
|
|
3
|
+
### Patch Changes
|
|
4
|
+
|
|
5
|
+
- 4a9dcd2: `admin:listUsers` now pages, counts and can carry roles.
|
|
6
|
+
|
|
7
|
+
`ListUsersInput` gains `offset` and `includeRoles`; `ListUsersOutput` gains `total`, and each `User` gains `roles` and `fields`. `total` is how many users match `search`, which is what a pager counts against — `users.length` never was, because it is capped by `limit`.
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
const { users, total } = await rpc.invoke('admin:listUsers', {
|
|
11
|
+
search: 'example.com',
|
|
12
|
+
limit: 50,
|
|
13
|
+
offset: 50,
|
|
14
|
+
includeRoles: true,
|
|
15
|
+
})
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Paging only means something over a stable order, so the query now sorts newest first rather than however the database felt like returning rows.
|
|
19
|
+
|
|
20
|
+
Synthetic principals — the platform credential owner, Fabric service users, scenario actors — are excluded by the query instead of dropped from the page afterwards. Filtering after the fact broke both halves of paging: `limit` had already counted the rows it then discarded, so a page came back short, and `offset` skipped synthetic rows as though they were people, so the same person could appear on two pages or on none.
|
|
21
|
+
|
|
22
|
+
`includeRoles` is refused without `admin:scopes:read`. `admin:users:list` says who may see the directory; it does not say who may see what each of those users can do.
|
|
23
|
+
|
|
24
|
+
`ScopeService` gains `listRolesForUsers(userIds)`, implemented in `@pikku/kysely`. It answers for every id asked for — an empty array for a user holding no roles, so a caller cannot read a missing key as "holds nothing" — and chunks its `in` list to stay inside the bound-parameter cap. A page of users used to cost one query per row, which on a database reached over the network is a round trip per row.
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
listRolesForUsers(userIds: string[]): Promise<Record<string, string[]>>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Anything implementing `ScopeService` outside this repository has to add it.
|
|
31
|
+
|
|
32
|
+
- Updated dependencies [4a9dcd2]
|
|
33
|
+
- Updated dependencies [5ab24ad]
|
|
34
|
+
- Updated dependencies [42b7ac3]
|
|
35
|
+
- @pikku/core@0.12.120
|
|
36
|
+
|
|
1
37
|
## 0.13.27
|
|
2
38
|
|
|
3
39
|
### Patch Changes
|
|
@@ -61,6 +61,7 @@ export declare class KyselyScopeService implements ScopeService {
|
|
|
61
61
|
addUserToRole(userId: string, role: string, grantedBy?: string): Promise<void>;
|
|
62
62
|
removeUserFromRole(userId: string, role: string): Promise<void>;
|
|
63
63
|
listUserRoles(userId: string): Promise<string[]>;
|
|
64
|
+
listRolesForUsers(userIds: string[]): Promise<Record<string, string[]>>;
|
|
64
65
|
addScopeToUser(userId: string, scope: string, grantedBy?: string): Promise<void>;
|
|
65
66
|
removeScopeFromUser(userId: string, scope: string): Promise<void>;
|
|
66
67
|
listUserScopes(userId: string): Promise<string[]>;
|
|
@@ -236,6 +236,35 @@ export class KyselyScopeService {
|
|
|
236
236
|
.execute();
|
|
237
237
|
return rows.map((row) => row.role);
|
|
238
238
|
}
|
|
239
|
+
async listRolesForUsers(userIds) {
|
|
240
|
+
// Seeded with every id asked for, so a user holding no roles is an empty
|
|
241
|
+
// array rather than a missing key.
|
|
242
|
+
const byUser = {};
|
|
243
|
+
for (const userId of userIds) {
|
|
244
|
+
byUser[userId] = [];
|
|
245
|
+
}
|
|
246
|
+
const distinct = Object.keys(byUser);
|
|
247
|
+
if (distinct.length === 0) {
|
|
248
|
+
return byUser;
|
|
249
|
+
}
|
|
250
|
+
// Chunked because an `in` list is one bound parameter per id, and every
|
|
251
|
+
// engine caps those — SQLite at 999 on older builds, Postgres at 65535 for
|
|
252
|
+
// the whole statement. A caller asking for a page of users stays well
|
|
253
|
+
// inside one chunk; one asking for thousands still gets an answer instead
|
|
254
|
+
// of a driver error.
|
|
255
|
+
for (let i = 0; i < distinct.length; i += 500) {
|
|
256
|
+
const chunk = distinct.slice(i, i + 500);
|
|
257
|
+
const rows = await this.db
|
|
258
|
+
.selectFrom('pikkuUserRole')
|
|
259
|
+
.select(['userId', 'role'])
|
|
260
|
+
.where('userId', 'in', chunk)
|
|
261
|
+
.execute();
|
|
262
|
+
for (const row of rows) {
|
|
263
|
+
byUser[row.userId].push(row.role);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return byUser;
|
|
267
|
+
}
|
|
239
268
|
async addScopeToUser(userId, scope, grantedBy) {
|
|
240
269
|
await this.db
|
|
241
270
|
.insertInto('pikkuUserScope')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pikku/kysely",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.28",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/pikkujs/pikku.git",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"prepublishOnly": "bun run build"
|
|
27
27
|
},
|
|
28
28
|
"peerDependencies": {
|
|
29
|
-
"@pikku/core": "^0.12.
|
|
29
|
+
"@pikku/core": "^0.12.120"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"kysely": "^0.29.5"
|