@bunbase-ae/js 2.15.1-next.353.f61db4f → 2.16.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/package.json +1 -1
- package/src/admin.ts +151 -2
- package/src/index.ts +4 -0
- package/src/types.ts +32 -0
package/package.json
CHANGED
package/src/admin.ts
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
// client.admin.system — health + stats
|
|
15
15
|
|
|
16
16
|
import type { HttpClient } from "./http";
|
|
17
|
-
import { BunBaseError, type Filter } from "./types";
|
|
17
|
+
import { BunBaseError, type Filter, type SequenceAccessRule, type SequenceMeta } from "./types";
|
|
18
18
|
|
|
19
19
|
// ─── Shared admin types ───────────────────────────────────────────────────────
|
|
20
20
|
|
|
@@ -182,6 +182,12 @@ export interface AdminStoredFile {
|
|
|
182
182
|
record_id: string | null;
|
|
183
183
|
owner_id: string | null;
|
|
184
184
|
owner_email?: string | null;
|
|
185
|
+
/**
|
|
186
|
+
* Tenant the file was uploaded under. `null` for admin uploads and
|
|
187
|
+
* pre-#536 rows; operators can attach a legacy null row to a tenant via
|
|
188
|
+
* `client.admin.storage.assignTenant(id, tenantId)`.
|
|
189
|
+
*/
|
|
190
|
+
tenant_id: string | null;
|
|
185
191
|
size: number;
|
|
186
192
|
mime_type: string;
|
|
187
193
|
is_public: boolean;
|
|
@@ -831,17 +837,53 @@ class AdminRelationsClient {
|
|
|
831
837
|
}
|
|
832
838
|
}
|
|
833
839
|
|
|
840
|
+
/**
|
|
841
|
+
* Sentinel querystring value for the `?tenant_id=` admin storage filter that
|
|
842
|
+
* matches `_files.tenant_id IS NULL` — the bucket of admin uploads and
|
|
843
|
+
* pre-#536 rows. Mirrors `_global` from the sequences admin routes (#537).
|
|
844
|
+
*/
|
|
845
|
+
const STORAGE_GLOBAL_TENANT_SENTINEL = "_global";
|
|
846
|
+
|
|
834
847
|
class AdminStorageClient {
|
|
835
848
|
constructor(private readonly http: HttpClient) {}
|
|
836
849
|
|
|
837
|
-
|
|
850
|
+
/**
|
|
851
|
+
* List admin-visible files, optionally scoped to a tenant.
|
|
852
|
+
*
|
|
853
|
+
* - omit `tenantId` → all files (no tenant filter, the legacy behavior).
|
|
854
|
+
* - pass a tenant id string → only that tenant's files.
|
|
855
|
+
* - pass `null` → only global / pre-#536 rows (the rows operators need to
|
|
856
|
+
* migrate when upgrading past v2.14.3).
|
|
857
|
+
*/
|
|
858
|
+
async listFiles(opts: { tenantId?: string | null } = {}): Promise<AdminStoredFile[]> {
|
|
859
|
+
const query: Record<string, string> = {};
|
|
860
|
+
if (opts.tenantId === null) {
|
|
861
|
+
query.tenant_id = STORAGE_GLOBAL_TENANT_SENTINEL;
|
|
862
|
+
} else if (typeof opts.tenantId === "string" && opts.tenantId !== "") {
|
|
863
|
+
query.tenant_id = opts.tenantId;
|
|
864
|
+
}
|
|
838
865
|
const res = await this.http.request<{ items: AdminStoredFile[] }>(
|
|
839
866
|
"GET",
|
|
840
867
|
"/api/v1/admin/storage",
|
|
868
|
+
Object.keys(query).length ? { query } : undefined,
|
|
841
869
|
);
|
|
842
870
|
return res.items;
|
|
843
871
|
}
|
|
844
872
|
|
|
873
|
+
/**
|
|
874
|
+
* Attach a file to a tenant (or unscope it). Used to migrate legacy
|
|
875
|
+
* admin uploads (pre-#536, `tenant_id IS NULL`) into a tenant context so
|
|
876
|
+
* tenant members can list/download/delete them under the post-#528 rules.
|
|
877
|
+
* Pass `null` to clear the assignment.
|
|
878
|
+
*/
|
|
879
|
+
async assignTenant(id: string, tenantId: string | null): Promise<AdminStoredFile> {
|
|
880
|
+
return this.http.request<AdminStoredFile>(
|
|
881
|
+
"PATCH",
|
|
882
|
+
`/api/v1/admin/storage/${encodeURIComponent(id)}/tenant`,
|
|
883
|
+
{ body: { tenant_id: tenantId } },
|
|
884
|
+
);
|
|
885
|
+
}
|
|
886
|
+
|
|
845
887
|
async uploadFile(params: {
|
|
846
888
|
file: File | Blob;
|
|
847
889
|
filename?: string;
|
|
@@ -1327,6 +1369,111 @@ class AdminTenantsClient {
|
|
|
1327
1369
|
}
|
|
1328
1370
|
}
|
|
1329
1371
|
|
|
1372
|
+
// ─── Sequences ────────────────────────────────────────────────────────────────
|
|
1373
|
+
|
|
1374
|
+
export interface CreateSequenceInput {
|
|
1375
|
+
name: string;
|
|
1376
|
+
/** Initial value. Defaults to 1 server-side. */
|
|
1377
|
+
start_value?: number;
|
|
1378
|
+
/** Access rule for runtime `/next` and `/peek`. Defaults to "authenticated". */
|
|
1379
|
+
access_next?: SequenceAccessRule;
|
|
1380
|
+
/**
|
|
1381
|
+
* When set, the sequence is scoped to that tenant — only callers whose JWT
|
|
1382
|
+
* carries a matching tenantId claim may call /next or /peek. When omitted
|
|
1383
|
+
* (or null), the sequence is global / admin-only.
|
|
1384
|
+
*/
|
|
1385
|
+
tenant_id?: string | null;
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
/**
|
|
1389
|
+
* Tenant scope for an admin mutation. The server permits multiple sequences
|
|
1390
|
+
* per name (one per tenant plus an optional global row); `tenant_id`
|
|
1391
|
+
* disambiguates which row the mutation targets.
|
|
1392
|
+
*
|
|
1393
|
+
* `tenant_id` is **omitted** by default — that targets the global row (the
|
|
1394
|
+
* server treats a missing `tenant_id` querystring as the `_global` sentinel,
|
|
1395
|
+
* the safer default established by #537). Pass a real tenant id to target
|
|
1396
|
+
* that tenant's row.
|
|
1397
|
+
*/
|
|
1398
|
+
export interface SequenceTenantOptions {
|
|
1399
|
+
tenant_id?: string;
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
// Sequence administration — surfaces the /api/v1/admin/sequences/* endpoints
|
|
1403
|
+
// added in v2.14.0 (#424/#429 family + tenant-scoping in #537/#556) so
|
|
1404
|
+
// operators don't need to hand-roll fetch calls to provision per-tenant
|
|
1405
|
+
// counters or reset them on yearly rollovers.
|
|
1406
|
+
class AdminSequencesClient {
|
|
1407
|
+
constructor(private readonly http: HttpClient) {}
|
|
1408
|
+
|
|
1409
|
+
async list(): Promise<SequenceMeta[]> {
|
|
1410
|
+
const res = await this.http.request<{ items: SequenceMeta[] }>(
|
|
1411
|
+
"GET",
|
|
1412
|
+
"/api/v1/admin/sequences",
|
|
1413
|
+
);
|
|
1414
|
+
return res.items;
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
async create(input: CreateSequenceInput): Promise<SequenceMeta> {
|
|
1418
|
+
return this.http.request<SequenceMeta>("POST", "/api/v1/admin/sequences", {
|
|
1419
|
+
body: input,
|
|
1420
|
+
});
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
async get(name: string, opts: SequenceTenantOptions = {}): Promise<SequenceMeta | null> {
|
|
1424
|
+
try {
|
|
1425
|
+
return await this.http.request<SequenceMeta>(
|
|
1426
|
+
"GET",
|
|
1427
|
+
`/api/v1/admin/sequences/${encodeURIComponent(name)}`,
|
|
1428
|
+
{ query: tenantQuery(opts) },
|
|
1429
|
+
);
|
|
1430
|
+
} catch (err) {
|
|
1431
|
+
if (err instanceof BunBaseError && err.status === 404) return null;
|
|
1432
|
+
throw err;
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
async reset(
|
|
1437
|
+
name: string,
|
|
1438
|
+
value: number,
|
|
1439
|
+
opts: SequenceTenantOptions = {},
|
|
1440
|
+
): Promise<SequenceMeta> {
|
|
1441
|
+
return this.http.request<SequenceMeta>(
|
|
1442
|
+
"PATCH",
|
|
1443
|
+
`/api/v1/admin/sequences/${encodeURIComponent(name)}`,
|
|
1444
|
+
{ body: { value }, query: tenantQuery(opts) },
|
|
1445
|
+
);
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
async setAccessRule(
|
|
1449
|
+
name: string,
|
|
1450
|
+
access_next: SequenceAccessRule,
|
|
1451
|
+
opts: SequenceTenantOptions = {},
|
|
1452
|
+
): Promise<SequenceMeta> {
|
|
1453
|
+
return this.http.request<SequenceMeta>(
|
|
1454
|
+
"PATCH",
|
|
1455
|
+
`/api/v1/admin/sequences/${encodeURIComponent(name)}`,
|
|
1456
|
+
{ body: { access_next }, query: tenantQuery(opts) },
|
|
1457
|
+
);
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
async delete(name: string, opts: SequenceTenantOptions = {}): Promise<void> {
|
|
1461
|
+
await this.http.request("DELETE", `/api/v1/admin/sequences/${encodeURIComponent(name)}`, {
|
|
1462
|
+
query: tenantQuery(opts),
|
|
1463
|
+
});
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
// Build the tenant_id querystring for an admin sequence call.
|
|
1468
|
+
//
|
|
1469
|
+
// Omitting tenant_id from the request entirely makes the server target the
|
|
1470
|
+
// global row (the post-#537 default). We preserve that behavior — a missing
|
|
1471
|
+
// `tenant_id` option means no `?tenant_id=…` on the wire. Callers who want
|
|
1472
|
+
// a tenant-scoped row must explicitly pass `tenant_id: "<id>"`.
|
|
1473
|
+
function tenantQuery(opts: SequenceTenantOptions): Record<string, string> {
|
|
1474
|
+
return opts.tenant_id !== undefined ? { tenant_id: opts.tenant_id } : {};
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1330
1477
|
// ─── Main AdminClient ─────────────────────────────────────────────────────────
|
|
1331
1478
|
|
|
1332
1479
|
export class AdminClient {
|
|
@@ -1343,6 +1490,7 @@ export class AdminClient {
|
|
|
1343
1490
|
readonly logs: AdminLogsClient;
|
|
1344
1491
|
readonly system: AdminSystemClient;
|
|
1345
1492
|
readonly tenants: AdminTenantsClient;
|
|
1493
|
+
readonly sequences: AdminSequencesClient;
|
|
1346
1494
|
|
|
1347
1495
|
constructor(http: HttpClient) {
|
|
1348
1496
|
this.users = new AdminUsersClient(http);
|
|
@@ -1358,5 +1506,6 @@ export class AdminClient {
|
|
|
1358
1506
|
this.logs = new AdminLogsClient(http);
|
|
1359
1507
|
this.system = new AdminSystemClient(http);
|
|
1360
1508
|
this.tenants = new AdminTenantsClient(http);
|
|
1509
|
+
this.sequences = new AdminSequencesClient(http);
|
|
1361
1510
|
}
|
|
1362
1511
|
}
|
package/src/index.ts
CHANGED
|
@@ -22,6 +22,7 @@ export {
|
|
|
22
22
|
type CreateHookInput,
|
|
23
23
|
type CreateNamedQueryInput,
|
|
24
24
|
type CreateRelationParams,
|
|
25
|
+
type CreateSequenceInput,
|
|
25
26
|
type EmailTemplate,
|
|
26
27
|
type HealthResponse,
|
|
27
28
|
type HookConfig,
|
|
@@ -37,6 +38,7 @@ export {
|
|
|
37
38
|
type Relation,
|
|
38
39
|
type RelationOnDelete,
|
|
39
40
|
type RelationType,
|
|
41
|
+
type SequenceTenantOptions,
|
|
40
42
|
type ServerSettings,
|
|
41
43
|
type SettingsAuditEntry,
|
|
42
44
|
type StatsResponse,
|
|
@@ -90,6 +92,8 @@ export {
|
|
|
90
92
|
type RealtimeCallback,
|
|
91
93
|
type RealtimeEvent,
|
|
92
94
|
type RealtimeEventType,
|
|
95
|
+
type SequenceAccessRule,
|
|
96
|
+
type SequenceMeta,
|
|
93
97
|
type StorageAdapter,
|
|
94
98
|
type SwitchTenantResult,
|
|
95
99
|
type TenantMembership,
|
package/src/types.ts
CHANGED
|
@@ -370,6 +370,38 @@ export interface ApiKey {
|
|
|
370
370
|
created_at: number;
|
|
371
371
|
}
|
|
372
372
|
|
|
373
|
+
// ─── Sequences ────────────────────────────────────────────────────────────────
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Access rule controlling who may call the runtime `next`/`peek` endpoints
|
|
377
|
+
* for a sequence.
|
|
378
|
+
*
|
|
379
|
+
* Mirrors the server's AccessRule shape:
|
|
380
|
+
* - "public" — anyone (no auth required)
|
|
381
|
+
* - "authenticated" — any logged-in user (default)
|
|
382
|
+
* - "disabled" — runtime calls always denied (admin-only sequence)
|
|
383
|
+
* - { role: "..." } — only users with that role
|
|
384
|
+
*/
|
|
385
|
+
export type SequenceAccessRule = "public" | "authenticated" | "disabled" | { role: string };
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* A sequence row as returned by the admin API. The wire shape exactly matches
|
|
389
|
+
* `SequenceMeta` exported from `@bunbase/core`.
|
|
390
|
+
*
|
|
391
|
+
* `tenant_id === null` is the global row (admin-only). A non-null `tenant_id`
|
|
392
|
+
* scopes the sequence to that tenant — only callers whose JWT carries a
|
|
393
|
+
* matching `tenantId` claim can call `/next` or `/peek` on it.
|
|
394
|
+
*/
|
|
395
|
+
export interface SequenceMeta {
|
|
396
|
+
name: string;
|
|
397
|
+
tenant_id: string | null;
|
|
398
|
+
value: number;
|
|
399
|
+
start_value: number;
|
|
400
|
+
access_next: SequenceAccessRule;
|
|
401
|
+
created_at: number;
|
|
402
|
+
updated_at: number;
|
|
403
|
+
}
|
|
404
|
+
|
|
373
405
|
// ─── Multi-tenancy ────────────────────────────────────────────────────────────
|
|
374
406
|
|
|
375
407
|
/** A single tenant membership for the authenticated user. Returned by `AuthClient.listTenants()`. */
|