@usebetterdev/tenant-core 0.2.0-beta.9 → 0.3.0-beta.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 +28 -14
- package/dist/api.d.ts +4 -0
- package/dist/api.d.ts.map +1 -1
- package/dist/better-tenant.d.ts +9 -10
- package/dist/better-tenant.d.ts.map +1 -1
- package/dist/console-endpoints.d.ts +13 -0
- package/dist/console-endpoints.d.ts.map +1 -0
- package/dist/index.cjs +236 -105
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +235 -103
- package/dist/index.js.map +1 -1
- package/dist/resolver.d.ts +6 -5
- package/dist/resolver.d.ts.map +1 -1
- package/dist/telemetry.d.ts +1 -4
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/types.d.ts +32 -6
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -16,7 +16,6 @@ function getTelemetryTenantConfig(config) {
|
|
|
16
16
|
custom: !!r.custom
|
|
17
17
|
},
|
|
18
18
|
tenantTablesCount: 0,
|
|
19
|
-
hasGetTenantRepository: !!config.getTenantRepository,
|
|
20
19
|
loadTenant: config.loadTenant,
|
|
21
20
|
basePathSet: !!config.basePath,
|
|
22
21
|
plugins: (config.plugins ?? []).map((p) => String(p.id))
|
|
@@ -326,7 +325,6 @@ async function handleRequest(request, next, options) {
|
|
|
326
325
|
|
|
327
326
|
// src/api.ts
|
|
328
327
|
var DEFAULT_LIST_LIMIT = 50;
|
|
329
|
-
var MAX_LIST_LIMIT = 50;
|
|
330
328
|
function requireRunAsSystem(adapter) {
|
|
331
329
|
if (!adapter.runAsSystem) {
|
|
332
330
|
throw new Error(
|
|
@@ -335,17 +333,7 @@ function requireRunAsSystem(adapter) {
|
|
|
335
333
|
}
|
|
336
334
|
return adapter.runAsSystem;
|
|
337
335
|
}
|
|
338
|
-
function requireTenantRepository(getTenantRepository) {
|
|
339
|
-
if (!getTenantRepository) {
|
|
340
|
-
throw new Error(
|
|
341
|
-
"better-tenant: tenant.api requires getTenantRepository in config (adapter provides CRUD for tenants table)"
|
|
342
|
-
);
|
|
343
|
-
}
|
|
344
|
-
return getTenantRepository;
|
|
345
|
-
}
|
|
346
336
|
function createTenantApi(adapter, getTenantRepository) {
|
|
347
|
-
const runAsSystem2 = requireRunAsSystem(adapter);
|
|
348
|
-
const getRepository = requireTenantRepository(getTenantRepository);
|
|
349
337
|
return {
|
|
350
338
|
async createTenant(data) {
|
|
351
339
|
if (!data.name?.trim()) {
|
|
@@ -354,8 +342,9 @@ function createTenantApi(adapter, getTenantRepository) {
|
|
|
354
342
|
if (!data.slug?.trim()) {
|
|
355
343
|
throw new Error("better-tenant: createTenant requires slug");
|
|
356
344
|
}
|
|
345
|
+
const runAsSystem2 = requireRunAsSystem(adapter);
|
|
357
346
|
return runAsSystem2(
|
|
358
|
-
(database) =>
|
|
347
|
+
(database) => getTenantRepository(database).create({
|
|
359
348
|
name: data.name.trim(),
|
|
360
349
|
slug: data.slug.trim()
|
|
361
350
|
})
|
|
@@ -365,29 +354,44 @@ function createTenantApi(adapter, getTenantRepository) {
|
|
|
365
354
|
if (!tenantId?.trim()) {
|
|
366
355
|
throw new Error("better-tenant: updateTenant requires tenantId");
|
|
367
356
|
}
|
|
357
|
+
const runAsSystem2 = requireRunAsSystem(adapter);
|
|
368
358
|
return runAsSystem2(
|
|
369
|
-
(database) =>
|
|
359
|
+
(database) => getTenantRepository(database).update(tenantId, {
|
|
370
360
|
...data.name !== void 0 && { name: data.name },
|
|
371
361
|
...data.slug !== void 0 && { slug: data.slug }
|
|
372
362
|
})
|
|
373
363
|
);
|
|
374
364
|
},
|
|
375
365
|
async listTenants(options = {}) {
|
|
376
|
-
const limit =
|
|
377
|
-
options.limit ?? DEFAULT_LIST_LIMIT,
|
|
378
|
-
MAX_LIST_LIMIT
|
|
379
|
-
);
|
|
366
|
+
const limit = options.limit ?? DEFAULT_LIST_LIMIT;
|
|
380
367
|
const offset = Math.max(0, options.offset ?? 0);
|
|
368
|
+
const runAsSystem2 = requireRunAsSystem(adapter);
|
|
381
369
|
return runAsSystem2(
|
|
382
|
-
(database) =>
|
|
370
|
+
(database) => getTenantRepository(database).list({ limit, offset })
|
|
383
371
|
);
|
|
384
372
|
},
|
|
385
373
|
async deleteTenant(tenantId) {
|
|
386
374
|
if (!tenantId?.trim()) {
|
|
387
375
|
throw new Error("better-tenant: deleteTenant requires tenantId");
|
|
388
376
|
}
|
|
377
|
+
const runAsSystem2 = requireRunAsSystem(adapter);
|
|
378
|
+
return runAsSystem2(
|
|
379
|
+
(database) => getTenantRepository(database).delete(tenantId)
|
|
380
|
+
);
|
|
381
|
+
},
|
|
382
|
+
async getTenant(tenantId) {
|
|
383
|
+
if (!tenantId?.trim()) {
|
|
384
|
+
throw new Error("better-tenant: getTenant requires tenantId");
|
|
385
|
+
}
|
|
386
|
+
const runAsSystem2 = requireRunAsSystem(adapter);
|
|
387
|
+
return runAsSystem2(
|
|
388
|
+
(database) => getTenantRepository(database).getById(tenantId)
|
|
389
|
+
);
|
|
390
|
+
},
|
|
391
|
+
async countTenants() {
|
|
392
|
+
const runAsSystem2 = requireRunAsSystem(adapter);
|
|
389
393
|
return runAsSystem2(
|
|
390
|
-
(database) =>
|
|
394
|
+
(database) => getTenantRepository(database).count()
|
|
391
395
|
);
|
|
392
396
|
}
|
|
393
397
|
};
|
|
@@ -397,7 +401,7 @@ async function runAs(tenantId, adapter, fn) {
|
|
|
397
401
|
}
|
|
398
402
|
async function runAsSystem(adapter, fn) {
|
|
399
403
|
const run = requireRunAsSystem(adapter);
|
|
400
|
-
return runWithContext({
|
|
404
|
+
return runWithContext({ isSystem: true }, () => run(fn));
|
|
401
405
|
}
|
|
402
406
|
|
|
403
407
|
// src/resolver.ts
|
|
@@ -479,39 +483,7 @@ function decodeJwtPayload(token) {
|
|
|
479
483
|
return null;
|
|
480
484
|
}
|
|
481
485
|
}
|
|
482
|
-
function resolveFromJwt(request, config) {
|
|
483
|
-
const getToken = request.getToken;
|
|
484
|
-
if (!getToken) {
|
|
485
|
-
return void 0;
|
|
486
|
-
}
|
|
487
|
-
const token = typeof getToken === "function" ? getToken() : getToken;
|
|
488
|
-
const value = token instanceof Promise ? void 0 : token ?? void 0;
|
|
489
|
-
const resolved = value ?? void 0;
|
|
490
|
-
if (!resolved) {
|
|
491
|
-
return void 0;
|
|
492
|
-
}
|
|
493
|
-
const claim = typeof config === "string" ? config : config.claim;
|
|
494
|
-
if (!claim) {
|
|
495
|
-
return void 0;
|
|
496
|
-
}
|
|
497
|
-
const verifyToken = typeof config === "object" ? config.verifyToken : void 0;
|
|
498
|
-
let payload;
|
|
499
|
-
if (verifyToken) {
|
|
500
|
-
const result = verifyToken(resolved);
|
|
501
|
-
if (result instanceof Promise) {
|
|
502
|
-
return void 0;
|
|
503
|
-
}
|
|
504
|
-
payload = result;
|
|
505
|
-
} else {
|
|
506
|
-
payload = decodeJwtPayload(resolved);
|
|
507
|
-
}
|
|
508
|
-
if (!payload) {
|
|
509
|
-
return void 0;
|
|
510
|
-
}
|
|
511
|
-
const claimValue = payload[claim];
|
|
512
|
-
return typeof claimValue === "string" && claimValue.length > 0 ? claimValue : void 0;
|
|
513
|
-
}
|
|
514
|
-
async function resolveFromJwtAsync(request, config) {
|
|
486
|
+
async function resolveFromJwt(request, config) {
|
|
515
487
|
const getToken = request.getToken;
|
|
516
488
|
if (!getToken) {
|
|
517
489
|
return void 0;
|
|
@@ -539,40 +511,28 @@ async function resolveFromJwtAsync(request, config) {
|
|
|
539
511
|
const claimValue = payload[claim];
|
|
540
512
|
return typeof claimValue === "string" && claimValue.length > 0 ? claimValue : void 0;
|
|
541
513
|
}
|
|
542
|
-
function
|
|
514
|
+
function describeStrategies(config) {
|
|
515
|
+
const strategies = [];
|
|
543
516
|
if (config.header !== void 0) {
|
|
544
|
-
|
|
545
|
-
if (v !== void 0) {
|
|
546
|
-
return v;
|
|
547
|
-
}
|
|
517
|
+
strategies.push(`header '${config.header}'`);
|
|
548
518
|
}
|
|
549
519
|
if (config.path !== void 0) {
|
|
550
|
-
const
|
|
551
|
-
|
|
552
|
-
return v;
|
|
553
|
-
}
|
|
520
|
+
const pattern = typeof config.path === "string" ? config.path : config.path.pattern;
|
|
521
|
+
strategies.push(`path '${pattern}'`);
|
|
554
522
|
}
|
|
555
523
|
if (config.subdomain !== void 0 && config.subdomain !== false) {
|
|
556
|
-
|
|
557
|
-
if (v !== void 0) {
|
|
558
|
-
return v;
|
|
559
|
-
}
|
|
524
|
+
strategies.push("subdomain");
|
|
560
525
|
}
|
|
561
526
|
if (config.jwt !== void 0) {
|
|
562
|
-
const
|
|
563
|
-
|
|
564
|
-
return v;
|
|
565
|
-
}
|
|
527
|
+
const claim = typeof config.jwt === "string" ? config.jwt : config.jwt.claim;
|
|
528
|
+
strategies.push(`jwt claim '${claim}'`);
|
|
566
529
|
}
|
|
567
530
|
if (config.custom !== void 0) {
|
|
568
|
-
|
|
569
|
-
if (typeof v === "string" && v.length > 0) {
|
|
570
|
-
return v;
|
|
571
|
-
}
|
|
531
|
+
strategies.push("custom resolver");
|
|
572
532
|
}
|
|
573
|
-
return
|
|
533
|
+
return strategies;
|
|
574
534
|
}
|
|
575
|
-
async function
|
|
535
|
+
async function resolveTenant(request, config) {
|
|
576
536
|
if (config.header !== void 0) {
|
|
577
537
|
const v = resolveFromHeader(request, config.header);
|
|
578
538
|
if (v !== void 0) {
|
|
@@ -592,7 +552,7 @@ async function resolveTenantAsync(request, config) {
|
|
|
592
552
|
}
|
|
593
553
|
}
|
|
594
554
|
if (config.jwt !== void 0) {
|
|
595
|
-
const v = await
|
|
555
|
+
const v = await resolveFromJwt(request, config.jwt);
|
|
596
556
|
if (v !== void 0) {
|
|
597
557
|
return v;
|
|
598
558
|
}
|
|
@@ -606,41 +566,214 @@ async function resolveTenantAsync(request, config) {
|
|
|
606
566
|
return void 0;
|
|
607
567
|
}
|
|
608
568
|
|
|
569
|
+
// src/console-endpoints.ts
|
|
570
|
+
function toConsoleRequest(request) {
|
|
571
|
+
if (request && typeof request === "object") {
|
|
572
|
+
return request;
|
|
573
|
+
}
|
|
574
|
+
return {};
|
|
575
|
+
}
|
|
576
|
+
function parsePositiveInt(value, max) {
|
|
577
|
+
if (value === void 0) {
|
|
578
|
+
return void 0;
|
|
579
|
+
}
|
|
580
|
+
const parsed = Number(value);
|
|
581
|
+
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
582
|
+
return void 0;
|
|
583
|
+
}
|
|
584
|
+
return Math.min(Math.floor(parsed), max);
|
|
585
|
+
}
|
|
586
|
+
function createTenantConsoleEndpoints(api) {
|
|
587
|
+
return [
|
|
588
|
+
{
|
|
589
|
+
method: "GET",
|
|
590
|
+
path: "/tenants",
|
|
591
|
+
requiredPermission: "read",
|
|
592
|
+
async handler(request) {
|
|
593
|
+
try {
|
|
594
|
+
const { query } = toConsoleRequest(request);
|
|
595
|
+
const limit = parsePositiveInt(query?.limit, 1e3);
|
|
596
|
+
const offset = parsePositiveInt(query?.offset, Number.MAX_SAFE_INTEGER);
|
|
597
|
+
const options = {};
|
|
598
|
+
if (limit !== void 0) {
|
|
599
|
+
options.limit = limit;
|
|
600
|
+
}
|
|
601
|
+
if (offset !== void 0) {
|
|
602
|
+
options.offset = offset;
|
|
603
|
+
}
|
|
604
|
+
const tenants = await api.listTenants(options);
|
|
605
|
+
return { status: 200, body: tenants };
|
|
606
|
+
} catch (error) {
|
|
607
|
+
return { status: 500, body: { error: "Internal server error" } };
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
},
|
|
611
|
+
{
|
|
612
|
+
method: "GET",
|
|
613
|
+
path: "/tenants/:id",
|
|
614
|
+
requiredPermission: "read",
|
|
615
|
+
async handler(request) {
|
|
616
|
+
try {
|
|
617
|
+
const { params } = toConsoleRequest(request);
|
|
618
|
+
const id = params?.id?.trim();
|
|
619
|
+
if (!id) {
|
|
620
|
+
return { status: 400, body: { error: "Missing tenant id" } };
|
|
621
|
+
}
|
|
622
|
+
const tenant = await api.getTenant(id);
|
|
623
|
+
if (!tenant) {
|
|
624
|
+
return { status: 404, body: { error: "Tenant not found" } };
|
|
625
|
+
}
|
|
626
|
+
return { status: 200, body: tenant };
|
|
627
|
+
} catch {
|
|
628
|
+
return { status: 500, body: { error: "Internal server error" } };
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
},
|
|
632
|
+
{
|
|
633
|
+
method: "POST",
|
|
634
|
+
path: "/tenants",
|
|
635
|
+
requiredPermission: "write",
|
|
636
|
+
async handler(request) {
|
|
637
|
+
try {
|
|
638
|
+
const { body } = toConsoleRequest(request);
|
|
639
|
+
if (!body || typeof body !== "object") {
|
|
640
|
+
return { status: 400, body: { error: "Request body is required" } };
|
|
641
|
+
}
|
|
642
|
+
const name = typeof body.name === "string" ? body.name : "";
|
|
643
|
+
const slug = typeof body.slug === "string" ? body.slug : "";
|
|
644
|
+
if (!name.trim() || !slug.trim()) {
|
|
645
|
+
return { status: 400, body: { error: "name and slug are required" } };
|
|
646
|
+
}
|
|
647
|
+
const tenant = await api.createTenant({ name, slug });
|
|
648
|
+
return { status: 201, body: tenant };
|
|
649
|
+
} catch {
|
|
650
|
+
return { status: 500, body: { error: "Internal server error" } };
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
},
|
|
654
|
+
{
|
|
655
|
+
method: "PATCH",
|
|
656
|
+
path: "/tenants/:id",
|
|
657
|
+
requiredPermission: "write",
|
|
658
|
+
async handler(request) {
|
|
659
|
+
try {
|
|
660
|
+
const { params, body } = toConsoleRequest(request);
|
|
661
|
+
const id = params?.id?.trim();
|
|
662
|
+
if (!id) {
|
|
663
|
+
return { status: 400, body: { error: "Missing tenant id" } };
|
|
664
|
+
}
|
|
665
|
+
if (!body || typeof body !== "object") {
|
|
666
|
+
return { status: 400, body: { error: "Request body is required" } };
|
|
667
|
+
}
|
|
668
|
+
const data = {};
|
|
669
|
+
if (typeof body.name === "string") {
|
|
670
|
+
data.name = body.name;
|
|
671
|
+
}
|
|
672
|
+
if (typeof body.slug === "string") {
|
|
673
|
+
data.slug = body.slug;
|
|
674
|
+
}
|
|
675
|
+
const tenant = await api.updateTenant(id, data);
|
|
676
|
+
return { status: 200, body: tenant };
|
|
677
|
+
} catch {
|
|
678
|
+
return { status: 500, body: { error: "Internal server error" } };
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
},
|
|
682
|
+
{
|
|
683
|
+
method: "DELETE",
|
|
684
|
+
path: "/tenants/:id",
|
|
685
|
+
requiredPermission: "admin",
|
|
686
|
+
async handler(request) {
|
|
687
|
+
try {
|
|
688
|
+
const { params } = toConsoleRequest(request);
|
|
689
|
+
const id = params?.id?.trim();
|
|
690
|
+
if (!id) {
|
|
691
|
+
return { status: 400, body: { error: "Missing tenant id" } };
|
|
692
|
+
}
|
|
693
|
+
await api.deleteTenant(id);
|
|
694
|
+
return { status: 204, body: null };
|
|
695
|
+
} catch {
|
|
696
|
+
return { status: 500, body: { error: "Internal server error" } };
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
},
|
|
700
|
+
{
|
|
701
|
+
method: "GET",
|
|
702
|
+
path: "/stats",
|
|
703
|
+
requiredPermission: "read",
|
|
704
|
+
async handler() {
|
|
705
|
+
try {
|
|
706
|
+
const tenantCount = await api.countTenants();
|
|
707
|
+
return { status: 200, body: { tenantCount } };
|
|
708
|
+
} catch (error) {
|
|
709
|
+
return { status: 500, body: { error: "Internal server error" } };
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
];
|
|
714
|
+
}
|
|
715
|
+
|
|
609
716
|
// src/better-tenant.ts
|
|
717
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
718
|
+
async function resolveIdentifierToId(identifier, resolverConfig, adapter, getTenantRepository) {
|
|
719
|
+
if (resolverConfig.resolveToId) {
|
|
720
|
+
return resolverConfig.resolveToId(identifier);
|
|
721
|
+
}
|
|
722
|
+
if (UUID_RE.test(identifier)) {
|
|
723
|
+
return identifier;
|
|
724
|
+
}
|
|
725
|
+
if (adapter.runAsSystem) {
|
|
726
|
+
const tenant = await adapter.runAsSystem(
|
|
727
|
+
(systemDb) => getTenantRepository(systemDb).getBySlug(identifier)
|
|
728
|
+
);
|
|
729
|
+
return tenant?.id;
|
|
730
|
+
}
|
|
731
|
+
return identifier;
|
|
732
|
+
}
|
|
610
733
|
function betterTenant(config) {
|
|
611
|
-
const {
|
|
734
|
+
const { database, tenantResolver, loadTenant } = config;
|
|
735
|
+
const { adapter, getTenantRepository } = database;
|
|
612
736
|
sendInitTelemetry(config, config.telemetry);
|
|
613
|
-
const api =
|
|
614
|
-
const
|
|
615
|
-
const
|
|
737
|
+
const api = createTenantApi(adapter, getTenantRepository);
|
|
738
|
+
const runWithTenantAndDatabaseOptions = loadTenant !== false ? { loadTenant: true, getTenantRepository } : void 0;
|
|
739
|
+
const resolverStrategies = describeStrategies(tenantResolver);
|
|
740
|
+
async function resolveAndNormalize(request) {
|
|
741
|
+
const raw = await resolveTenant(request, tenantResolver);
|
|
742
|
+
if (!raw) return void 0;
|
|
743
|
+
return resolveIdentifierToId(
|
|
744
|
+
raw,
|
|
745
|
+
tenantResolver,
|
|
746
|
+
adapter,
|
|
747
|
+
getTenantRepository
|
|
748
|
+
);
|
|
749
|
+
}
|
|
750
|
+
if (config.console) {
|
|
751
|
+
const endpoints = createTenantConsoleEndpoints(api);
|
|
752
|
+
config.console.registerProduct({
|
|
753
|
+
id: "tenant",
|
|
754
|
+
name: "Better Tenant",
|
|
755
|
+
endpoints
|
|
756
|
+
});
|
|
757
|
+
}
|
|
616
758
|
return {
|
|
617
759
|
getContext,
|
|
618
760
|
getDatabase: () => getDatabase(),
|
|
619
761
|
runWithTenant,
|
|
620
|
-
|
|
621
|
-
|
|
762
|
+
runAs: (tenantId, fn) => runWithTenantAndDatabase(
|
|
763
|
+
tenantId,
|
|
764
|
+
adapter,
|
|
765
|
+
fn,
|
|
766
|
+
runWithTenantAndDatabaseOptions
|
|
767
|
+
),
|
|
622
768
|
runAsSystem: (fn) => runAsSystem(adapter, fn),
|
|
623
|
-
resolveTenant:
|
|
624
|
-
|
|
769
|
+
resolveTenant: resolveAndNormalize,
|
|
770
|
+
resolverStrategies,
|
|
625
771
|
handleRequest: (request, next, options) => handleRequest(request, next, {
|
|
626
772
|
...options,
|
|
627
|
-
resolveTenant:
|
|
773
|
+
resolveTenant: resolveAndNormalize,
|
|
628
774
|
adapter
|
|
629
775
|
}),
|
|
630
|
-
|
|
631
|
-
};
|
|
632
|
-
}
|
|
633
|
-
function createStubTenantApi() {
|
|
634
|
-
const err = () => {
|
|
635
|
-
throw new Error(
|
|
636
|
-
"better-tenant: tenant.api requires getTenantRepository in config"
|
|
637
|
-
);
|
|
638
|
-
};
|
|
639
|
-
return {
|
|
640
|
-
createTenant: () => err(),
|
|
641
|
-
updateTenant: () => err(),
|
|
642
|
-
listTenants: () => err(),
|
|
643
|
-
deleteTenant: () => err()
|
|
776
|
+
api
|
|
644
777
|
};
|
|
645
778
|
}
|
|
646
779
|
|
|
@@ -733,14 +866,13 @@ export {
|
|
|
733
866
|
TenantNotResolvedError,
|
|
734
867
|
betterTenant,
|
|
735
868
|
createTenantApi,
|
|
869
|
+
describeStrategies,
|
|
736
870
|
getContext,
|
|
737
871
|
handleRequest,
|
|
738
872
|
resolveTenant,
|
|
739
|
-
resolveTenantAsync,
|
|
740
873
|
runAs,
|
|
741
874
|
runAsSystem,
|
|
742
875
|
runWithTenant,
|
|
743
|
-
runWithTenantAndDatabase,
|
|
744
876
|
sendCliTelemetry,
|
|
745
877
|
toResolvableRequest
|
|
746
878
|
};
|