@classytic/arc 2.15.3 → 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/README.md +1 -0
- package/bin/arc.js +12 -0
- package/dist/{BaseController-dx3m2J8V.mjs → BaseController-DlCCTIxJ.mjs} +61 -19
- package/dist/{HookSystem-Iiebom92.mjs → HookSystem-Cmf7-Etp.mjs} +8 -4
- package/dist/{QueryCache-D41bfdBB.d.mts → QueryCache-SvmT_9ti.d.mts} +1 -1
- package/dist/{ResourceRegistry-CTERg_2x.mjs → ResourceRegistry-f48hFk3m.mjs} +52 -9
- package/dist/audit/index.d.mts +1 -1
- package/dist/audit/index.mjs +4 -2
- package/dist/auth/index.d.mts +4 -4
- package/dist/auth/index.mjs +4 -4
- package/dist/auth/redis-session.d.mts +1 -1
- package/dist/{betterAuthOpenApi--M_i87dQ.mjs → betterAuthOpenApi-ClWxaceA.mjs} +10 -6
- package/dist/buildHandler-BZX6zzDM.mjs +300 -0
- package/dist/cache/index.d.mts +3 -3
- package/dist/cache/index.mjs +3 -3
- package/dist/{caching-SM8gghN6.mjs → caching-TeHE8G-v.mjs} +1 -1
- package/dist/cli/commands/describe.d.mts +35 -1
- package/dist/cli/commands/describe.mjs +52 -12
- package/dist/cli/commands/docs.d.mts +1 -4
- package/dist/cli/commands/docs.mjs +4 -16
- package/dist/cli/commands/generate.d.mts +2 -20
- package/dist/cli/commands/generate.mjs +1 -546
- package/dist/cli/commands/init.d.mts +2 -40
- package/dist/cli/commands/init.mjs +1 -3036
- package/dist/cli/commands/introspect.mjs +53 -64
- package/dist/cli/index.d.mts +2 -2
- package/dist/cli/index.mjs +2 -2
- package/dist/{constants-Cxde4rpC.mjs → constants-TrJVIJl0.mjs} +7 -0
- package/dist/core/index.d.mts +3 -3
- package/dist/core/index.mjs +5 -5
- package/dist/{core-CvmOqEms.mjs → core-DBJ_j6rX.mjs} +222 -44
- package/dist/createActionRouter-DUpN3Dd1.mjs +288 -0
- package/dist/{createAggregationRouter-B0bPDf5b.mjs → createAggregationRouter-Dq-TUCuY.mjs} +3 -2
- package/dist/{createApp-PFegs47-.mjs → createApp-DNccuhyI.mjs} +16 -14
- package/dist/{defineEvent-D5h7EvAx.mjs → defineEvent-DRwY0fYm.mjs} +1 -1
- package/dist/docs/index.d.mts +2 -2
- package/dist/docs/index.mjs +1 -1
- package/dist/{errorHandler-Bk-AGhkU.mjs → errorHandler-DpoXQHZ9.mjs} +17 -14
- package/dist/errors-C1lX_jlm.d.mts +91 -0
- package/dist/{eventPlugin-CaKTYkYM.mjs → eventPlugin-C2cGqtRO.mjs} +1 -1
- package/dist/{eventPlugin-qXpqTebY.d.mts → eventPlugin-CtHC_av1.d.mts} +1 -1
- package/dist/events/index.d.mts +3 -3
- package/dist/events/index.mjs +5 -5
- package/dist/events/transports/redis-stream-entry.d.mts +1 -1
- package/dist/events/transports/redis.d.mts +1 -1
- package/dist/factory/index.d.mts +1 -1
- package/dist/factory/index.mjs +2 -2
- package/dist/{fields-COhcH3fk.d.mts → fields-Anj0xdih.d.mts} +1 -1
- package/dist/generate-BWFwgcCM.d.mts +38 -0
- package/dist/generate-CYac-OLv.mjs +654 -0
- package/dist/hooks/index.d.mts +1 -1
- package/dist/hooks/index.mjs +1 -1
- package/dist/idempotency/index.d.mts +2 -2
- package/dist/idempotency/index.mjs +1 -1
- package/dist/idempotency/redis.d.mts +1 -1
- package/dist/{index-BTqLEvhu.d.mts → index-3oIimXQn.d.mts} +12 -12
- package/dist/{index-BstGxcc3.d.mts → index-B-ulKx5P.d.mts} +55 -4
- package/dist/{index-BswOSJCE.d.mts → index-CkW0flkU.d.mts} +355 -16
- package/dist/index.d.mts +6 -6
- package/dist/index.mjs +7 -8
- package/dist/init-Dv71MsJr.d.mts +71 -0
- package/dist/init-HDvoO9L5.mjs +3098 -0
- package/dist/integrations/event-gateway.d.mts +2 -2
- package/dist/integrations/event-gateway.mjs +1 -1
- package/dist/integrations/index.d.mts +2 -2
- package/dist/integrations/jobs.mjs +3 -3
- package/dist/integrations/mcp/index.d.mts +239 -7
- package/dist/integrations/mcp/index.mjs +2 -528
- package/dist/integrations/mcp/testing.d.mts +2 -2
- package/dist/integrations/mcp/testing.mjs +6 -10
- package/dist/integrations/streamline.d.mts +71 -2
- package/dist/integrations/streamline.mjs +81 -8
- package/dist/integrations/websocket-redis.d.mts +1 -1
- package/dist/integrations/websocket.d.mts +1 -1
- package/dist/integrations/websocket.mjs +1 -0
- package/dist/loadResourcesFromEntry-BLMEI2Xa.mjs +51 -0
- package/dist/{resourceToTools-tFYUNmM0.mjs → mcpPlugin-7vGV51ED.mjs} +1021 -318
- package/dist/{memory-UBydS5ku.mjs → memory-QOLe11D5.mjs} +2 -0
- package/dist/middleware/index.d.mts +1 -1
- package/dist/middleware/index.mjs +1 -1
- package/dist/{openapi-BHXhoX8O.mjs → openapi-34T9yNwd.mjs} +47 -36
- package/dist/permissions/index.d.mts +2 -2
- package/dist/permissions/index.mjs +1 -1
- package/dist/{permissions-ohQyv50e.mjs → permissions-CTxMrreC.mjs} +2 -2
- package/dist/{pipe-Zr0KXjQe.mjs → pipe-DiCyvyPN.mjs} +1 -0
- package/dist/pipeline/index.d.mts +1 -1
- package/dist/pipeline/index.mjs +1 -1
- package/dist/plugins/index.d.mts +5 -5
- package/dist/plugins/index.mjs +10 -10
- package/dist/plugins/response-cache.mjs +5 -5
- package/dist/plugins/tracing-entry.d.mts +1 -1
- package/dist/plugins/tracing-entry.mjs +1 -1
- package/dist/{pluralize-DQgqgifU.mjs → pluralize-B9M8xvy-.mjs} +2 -1
- package/dist/presets/filesUpload.d.mts +4 -4
- package/dist/presets/filesUpload.mjs +2 -2
- package/dist/presets/index.d.mts +1 -1
- package/dist/presets/index.mjs +1 -1
- package/dist/presets/multiTenant.d.mts +1 -1
- package/dist/presets/multiTenant.mjs +4 -3
- package/dist/presets/search.d.mts +2 -2
- package/dist/presets/search.mjs +1 -1
- package/dist/{presets-BbkjdPeH.mjs → presets-C9BE6WaZ.mjs} +2 -2
- package/dist/{queryCachePlugin-m1XsgAIJ.mjs → queryCachePlugin-B4XMSSe7.mjs} +2 -2
- package/dist/{queryCachePlugin-CqMdLI2-.d.mts → queryCachePlugin-Biqzfbi5.d.mts} +2 -2
- package/dist/{redis-DiMkdHEl.d.mts → redis-Cyzrz6SX.d.mts} +1 -1
- package/dist/{redis-stream-D6HzR1Z_.d.mts → redis-stream-DT-YjzrB.d.mts} +1 -1
- package/dist/registry/index.d.mts +319 -2
- package/dist/registry/index.mjs +3 -3
- package/dist/registry-BBE23CDj.mjs +576 -0
- package/dist/{routerShared-DrOa-26E.mjs → routerShared-CZV5aabX.mjs} +3 -3
- package/dist/scope/index.d.mts +3 -3
- package/dist/scope/index.mjs +3 -3
- package/dist/{sse-Bz-5ZeTt.mjs → sse-BY6sTy4P.mjs} +1 -1
- package/dist/testing/index.d.mts +2 -2
- package/dist/testing/index.mjs +16 -7
- package/dist/testing/storageContract.d.mts +1 -1
- package/dist/types/index.d.mts +5 -5
- package/dist/types/storage.d.mts +1 -1
- package/dist/{types-C_s5moIu.mjs → types-Bi0r0vjG.mjs} +53 -1
- package/dist/{types-BQsjgQzS.d.mts → types-BsJMEQ4D.d.mts} +106 -12
- package/dist/{types-DrBaUwyV.d.mts → types-D-fYtKjb.d.mts} +33 -10
- package/dist/{types-CTYvcwHe.d.mts → types-DVfpSfx2.d.mts} +42 -1
- package/dist/utils/index.d.mts +1286 -2
- package/dist/utils/index.mjs +1 -1
- package/dist/{utils-_h9B3c57.mjs → utils-DC5ycPfr.mjs} +89 -40
- package/dist/{buildHandler-CcFOpJLh.mjs → validate-By96rH0r.mjs} +8 -299
- package/dist/{versioning-hmkPcDlX.d.mts → versioning-ZwX9tmbS.d.mts} +1 -1
- package/package.json +22 -29
- package/skills/arc/SKILL.md +299 -689
- package/skills/arc/references/auth.md +19 -7
- package/skills/arc-code-review/SKILL.md +1 -1
- package/skills/arc-code-review/references/arc-cheatsheet.md +100 -322
- package/dist/createActionRouter-S3MLVYot.mjs +0 -220
- package/dist/index-bRjYu21O.d.mts +0 -1320
- package/dist/org/index.d.mts +0 -66
- package/dist/org/index.mjs +0 -486
- package/dist/org/types.d.mts +0 -82
- package/dist/org/types.mjs +0 -1
- package/dist/registry-I-ogLgL9.mjs +0 -46
- /package/dist/{EventTransport-CT_52aWU.d.mts → EventTransport-C-2oAHtw.d.mts} +0 -0
- /package/dist/{EventTransport-DLWoUMHy.mjs → EventTransport-Hxvv5QQz.mjs} +0 -0
- /package/dist/{actionPermissions-CyUkQu6O.mjs → actionPermissions-Bjmvn7Eb.mjs} +0 -0
- /package/dist/{elevation-BXOWoGCF.d.mts → elevation-0YBpa663.d.mts} +0 -0
- /package/dist/{elevation-DgoeTyfX.mjs → elevation-Dci0AYLT.mjs} +0 -0
- /package/dist/{errorHandler-DFr45ZG4.d.mts → errorHandler-mHuyWzZE.d.mts} +0 -0
- /package/dist/{externalPaths-BD5nw6St.d.mts → externalPaths-DFg-2KTp.d.mts} +0 -0
- /package/dist/{interface-beEtJyWM.d.mts → interface-CH0OQudo.d.mts} +0 -0
- /package/dist/{interface-DfLGcus7.d.mts → interface-NwJ_qPlY.d.mts} +0 -0
- /package/dist/{keys-CGcCbNyu.mjs → keys-DopsCuyQ.mjs} +0 -0
- /package/dist/{loadResources-DBMQg_Aj.mjs → loadResources-ChQEj8ih.mjs} +0 -0
- /package/dist/{metrics-Qnvwc-LQ.mjs → metrics-TuOmguhi.mjs} +0 -0
- /package/dist/{replyHelpers-CK-FNO8E.mjs → replyHelpers-C-gD32oF.mjs} +0 -0
- /package/dist/{schemaIR-lYhC2gE5.mjs → schemaIR-Ctc89DSn.mjs} +0 -0
- /package/dist/{sessionManager-C4Le_UB3.d.mts → sessionManager-BqFegc0W.d.mts} +0 -0
- /package/dist/{storage-Dfzt4VTl.d.mts → storage-D2KZJAmn.d.mts} +0 -0
- /package/dist/{store-helpers-BkIN9-vu.mjs → store-helpers-B0sunfZZ.mjs} +0 -0
- /package/dist/{tracing-QJVprktp.d.mts → tracing-Dm8n7Cnn.d.mts} +0 -0
- /package/dist/{versioning-BUrT5aP4.mjs → versioning-B6mimogM.mjs} +0 -0
- /package/dist/{websocket-ChC2rqe1.d.mts → websocket-BkjeGZRn.d.mts} +0 -0
package/dist/org/index.d.mts
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { yt as RouteHandler } from "../index-BswOSJCE.mjs";
|
|
2
|
-
import { d as UserBase } from "../fields-COhcH3fk.mjs";
|
|
3
|
-
import { InvitationAdapter, InvitationDoc, MemberDoc, OrgAdapter, OrgDoc, OrgPermissionStatement, OrgRole, OrganizationPluginOptions } from "./types.mjs";
|
|
4
|
-
import { FastifyPluginAsync, RouteHandlerMethod } from "fastify";
|
|
5
|
-
|
|
6
|
-
//#region src/org/organizationPlugin.d.ts
|
|
7
|
-
declare module "fastify" {
|
|
8
|
-
interface FastifyInstance {
|
|
9
|
-
/** Middleware: require the caller to hold one of the listed org roles */
|
|
10
|
-
requireOrgRole: (roles: string[]) => RouteHandlerMethod;
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
declare const organizationPlugin: FastifyPluginAsync<OrganizationPluginOptions>;
|
|
14
|
-
declare const _default: FastifyPluginAsync<OrganizationPluginOptions>;
|
|
15
|
-
//#endregion
|
|
16
|
-
//#region src/org/orgGuard.d.ts
|
|
17
|
-
interface OrgGuardOptions {
|
|
18
|
-
/** Require organization context (default: true) */
|
|
19
|
-
requireOrgContext?: boolean;
|
|
20
|
-
/** Required org-level roles */
|
|
21
|
-
roles?: string[];
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Create org guard middleware.
|
|
25
|
-
* Reads `request.scope` for org context and roles.
|
|
26
|
-
* Elevated scope always passes.
|
|
27
|
-
*/
|
|
28
|
-
declare function orgGuard(options?: OrgGuardOptions): RouteHandler;
|
|
29
|
-
/**
|
|
30
|
-
* Shorthand for requiring org context
|
|
31
|
-
*/
|
|
32
|
-
declare function requireOrg(): RouteHandler;
|
|
33
|
-
/**
|
|
34
|
-
* Require org context with specific roles
|
|
35
|
-
*/
|
|
36
|
-
declare function requireOrgRole(...roles: string[]): RouteHandler;
|
|
37
|
-
//#endregion
|
|
38
|
-
//#region src/org/orgMembership.d.ts
|
|
39
|
-
interface OrgMembershipOptions {
|
|
40
|
-
/** Path to user's organizations array */
|
|
41
|
-
userOrgsPath?: string;
|
|
42
|
-
/** Optional DB lookup function */
|
|
43
|
-
validateFromDb?: (userId: string, orgId: string) => Promise<boolean>;
|
|
44
|
-
}
|
|
45
|
-
interface OrgRolesOptions {
|
|
46
|
-
/** Path to user's organizations array */
|
|
47
|
-
userOrgsPath?: string;
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* Check if user is member of organization.
|
|
51
|
-
* This is a low-level utility for checking membership from user object data.
|
|
52
|
-
* For request-level checks, use `request.scope` (isMember/isElevated guards).
|
|
53
|
-
*/
|
|
54
|
-
declare function orgMembershipCheck(user: UserBase | undefined | null, orgId: string | undefined | null, options?: OrgMembershipOptions): Promise<boolean>;
|
|
55
|
-
/**
|
|
56
|
-
* Get user's role in organization from user object data.
|
|
57
|
-
* For request-level role checks, use `request.scope.orgRoles` (when scope is 'member').
|
|
58
|
-
*/
|
|
59
|
-
declare function getUserOrgRoles(user: UserBase | undefined | null, orgId: string | undefined | null, options?: OrgRolesOptions): string[];
|
|
60
|
-
/**
|
|
61
|
-
* Check if user has specific role in organization from user object data.
|
|
62
|
-
* For request-level role checks, use `requireOrgRole()` permission or `request.scope`.
|
|
63
|
-
*/
|
|
64
|
-
declare function hasOrgRole(user: UserBase | undefined | null, orgId: string | undefined | null, roles: string | string[], options?: OrgRolesOptions): boolean;
|
|
65
|
-
//#endregion
|
|
66
|
-
export { type InvitationAdapter, type InvitationDoc, type MemberDoc, type OrgAdapter, type OrgDoc, type OrgGuardOptions, type OrgMembershipOptions, type OrgPermissionStatement, type OrgRole, type OrgRolesOptions, type OrganizationPluginOptions, getUserOrgRoles, hasOrgRole, orgGuard, orgMembershipCheck, _default as organizationPlugin, organizationPlugin as organizationPluginFn, requireOrg, requireOrgRole };
|
package/dist/org/index.mjs
DELETED
|
@@ -1,486 +0,0 @@
|
|
|
1
|
-
import { _ as hasOrgAccess, b as isMember, l as getOrgRoles, n as PUBLIC_SCOPE, y as isElevated } from "../types-C_s5moIu.mjs";
|
|
2
|
-
import fp from "fastify-plugin";
|
|
3
|
-
//#region src/org/organizationPlugin.ts
|
|
4
|
-
const DEFAULT_ROLES = [
|
|
5
|
-
{
|
|
6
|
-
name: "owner",
|
|
7
|
-
permissions: [{
|
|
8
|
-
resource: "*",
|
|
9
|
-
action: ["*"]
|
|
10
|
-
}]
|
|
11
|
-
},
|
|
12
|
-
{
|
|
13
|
-
name: "admin",
|
|
14
|
-
permissions: [{
|
|
15
|
-
resource: "org",
|
|
16
|
-
action: ["read", "update"]
|
|
17
|
-
}, {
|
|
18
|
-
resource: "members",
|
|
19
|
-
action: ["*"]
|
|
20
|
-
}]
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
name: "member",
|
|
24
|
-
permissions: [{
|
|
25
|
-
resource: "org",
|
|
26
|
-
action: ["read"]
|
|
27
|
-
}, {
|
|
28
|
-
resource: "members",
|
|
29
|
-
action: ["read"]
|
|
30
|
-
}]
|
|
31
|
-
}
|
|
32
|
-
];
|
|
33
|
-
/** Extract a UserBase from the request (set by auth plugin). */
|
|
34
|
-
function getUser(request) {
|
|
35
|
-
return request.user;
|
|
36
|
-
}
|
|
37
|
-
/** Get user id (supports both `id` and `_id`). */
|
|
38
|
-
function getUserId(user) {
|
|
39
|
-
const raw = user.id ?? user._id;
|
|
40
|
-
return raw ? String(raw) : void 0;
|
|
41
|
-
}
|
|
42
|
-
/** Standard JSON error reply. */
|
|
43
|
-
function sendError(reply, statusCode, code, message) {
|
|
44
|
-
reply.code(statusCode).send({
|
|
45
|
-
success: false,
|
|
46
|
-
code,
|
|
47
|
-
error: message
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
const organizationPlugin = async (fastify, opts) => {
|
|
51
|
-
const { adapter, roles = DEFAULT_ROLES, basePath = "/api/organizations", enableInvitations = false } = opts;
|
|
52
|
-
const validRoleNames = new Set(roles.map((r) => r.name));
|
|
53
|
-
/**
|
|
54
|
-
* Create a preHandler that:
|
|
55
|
-
* 1. Ensures the request is authenticated
|
|
56
|
-
* 2. Looks up the caller's membership in the org identified by `:orgId`
|
|
57
|
-
* 3. Verifies the caller holds one of the required roles
|
|
58
|
-
*/
|
|
59
|
-
fastify.decorate("requireOrgRole", function requireOrgRole(requiredRoles) {
|
|
60
|
-
return async function requireOrgRoleHandler(request, reply) {
|
|
61
|
-
const user = getUser(request);
|
|
62
|
-
if (!user) {
|
|
63
|
-
sendError(reply, 401, "UNAUTHORIZED", "Authentication required");
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
const userId = getUserId(user);
|
|
67
|
-
if (!userId) {
|
|
68
|
-
sendError(reply, 401, "UNAUTHORIZED", "Unable to determine user identity");
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
const { orgId } = request.params;
|
|
72
|
-
if (!orgId) {
|
|
73
|
-
sendError(reply, 400, "MISSING_ORG_ID", "Organization ID is required");
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
const member = await adapter.getMember(orgId, userId);
|
|
77
|
-
if (!member) {
|
|
78
|
-
sendError(reply, 403, "NOT_A_MEMBER", "You are not a member of this organization");
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
if (!requiredRoles.includes(member.role)) {
|
|
82
|
-
sendError(reply, 403, "INSUFFICIENT_ROLE", `This action requires one of these roles: ${requiredRoles.join(", ")}`);
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
};
|
|
86
|
-
});
|
|
87
|
-
/** Wrap preHandlers so that authenticate is called first (if available). */
|
|
88
|
-
function withAuth(...extra) {
|
|
89
|
-
const handlers = [];
|
|
90
|
-
const inst = fastify;
|
|
91
|
-
if (typeof inst.authenticate === "function") handlers.push(inst.authenticate);
|
|
92
|
-
handlers.push(...extra);
|
|
93
|
-
return handlers;
|
|
94
|
-
}
|
|
95
|
-
function generateSlug(name) {
|
|
96
|
-
return name.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* POST / -- Create organization
|
|
100
|
-
*
|
|
101
|
-
* Body: { name: string; slug?: string; [key: string]: unknown }
|
|
102
|
-
* The authenticated user becomes the owner.
|
|
103
|
-
*/
|
|
104
|
-
fastify.post(basePath, { preHandler: withAuth() }, async (request, reply) => {
|
|
105
|
-
const user = getUser(request);
|
|
106
|
-
if (!user) {
|
|
107
|
-
sendError(reply, 401, "UNAUTHORIZED", "Authentication required");
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
const userId = getUserId(user);
|
|
111
|
-
if (!userId) {
|
|
112
|
-
sendError(reply, 401, "UNAUTHORIZED", "Unable to determine user identity");
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
const body = request.body;
|
|
116
|
-
if (!body?.name) {
|
|
117
|
-
sendError(reply, 400, "VALIDATION_ERROR", "Organization name is required");
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
const slug = body.slug ?? generateSlug(body.name);
|
|
121
|
-
if (await adapter.getOrgBySlug(slug)) {
|
|
122
|
-
sendError(reply, 409, "SLUG_TAKEN", `An organization with slug '${slug}' already exists`);
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
const org = await adapter.createOrg({
|
|
126
|
-
...body,
|
|
127
|
-
name: body.name,
|
|
128
|
-
slug,
|
|
129
|
-
ownerId: userId
|
|
130
|
-
});
|
|
131
|
-
await adapter.addMember(org.id, userId, "owner");
|
|
132
|
-
reply.code(201).send({
|
|
133
|
-
success: true,
|
|
134
|
-
data: org
|
|
135
|
-
});
|
|
136
|
-
});
|
|
137
|
-
/**
|
|
138
|
-
* GET / -- List the authenticated user's organizations
|
|
139
|
-
*/
|
|
140
|
-
fastify.get(basePath, { preHandler: withAuth() }, async (request, reply) => {
|
|
141
|
-
const user = getUser(request);
|
|
142
|
-
if (!user) {
|
|
143
|
-
sendError(reply, 401, "UNAUTHORIZED", "Authentication required");
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
const userId = getUserId(user);
|
|
147
|
-
if (!userId) {
|
|
148
|
-
sendError(reply, 401, "UNAUTHORIZED", "Unable to determine user identity");
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
const orgs = await adapter.listUserOrgs(userId);
|
|
152
|
-
reply.send({
|
|
153
|
-
success: true,
|
|
154
|
-
data: orgs
|
|
155
|
-
});
|
|
156
|
-
});
|
|
157
|
-
/**
|
|
158
|
-
* GET /:orgId -- Get a single organization
|
|
159
|
-
*/
|
|
160
|
-
fastify.get(`${basePath}/:orgId`, { preHandler: withAuth(fastify.requireOrgRole([
|
|
161
|
-
"owner",
|
|
162
|
-
"admin",
|
|
163
|
-
"member"
|
|
164
|
-
])) }, async (request, reply) => {
|
|
165
|
-
const { orgId } = request.params;
|
|
166
|
-
const org = await adapter.getOrg(orgId);
|
|
167
|
-
if (!org) {
|
|
168
|
-
sendError(reply, 404, "NOT_FOUND", "Organization not found");
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
reply.send({
|
|
172
|
-
success: true,
|
|
173
|
-
data: org
|
|
174
|
-
});
|
|
175
|
-
});
|
|
176
|
-
/**
|
|
177
|
-
* PATCH /:orgId -- Update organization
|
|
178
|
-
*/
|
|
179
|
-
fastify.patch(`${basePath}/:orgId`, { preHandler: withAuth(fastify.requireOrgRole(["owner", "admin"])) }, async (request, reply) => {
|
|
180
|
-
const { orgId } = request.params;
|
|
181
|
-
const body = request.body;
|
|
182
|
-
if (!body || Object.keys(body).length === 0) {
|
|
183
|
-
sendError(reply, 400, "VALIDATION_ERROR", "Request body must not be empty");
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
const { ownerId: _ownerId, id: _id, ...updates } = body;
|
|
187
|
-
const org = await adapter.updateOrg(orgId, updates);
|
|
188
|
-
if (!org) {
|
|
189
|
-
sendError(reply, 404, "NOT_FOUND", "Organization not found");
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
reply.send({
|
|
193
|
-
success: true,
|
|
194
|
-
data: org
|
|
195
|
-
});
|
|
196
|
-
});
|
|
197
|
-
/**
|
|
198
|
-
* DELETE /:orgId -- Delete organization (owner only)
|
|
199
|
-
*/
|
|
200
|
-
fastify.delete(`${basePath}/:orgId`, { preHandler: withAuth(fastify.requireOrgRole(["owner"])) }, async (request, reply) => {
|
|
201
|
-
const { orgId } = request.params;
|
|
202
|
-
if (!await adapter.getOrg(orgId)) {
|
|
203
|
-
sendError(reply, 404, "NOT_FOUND", "Organization not found");
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
await adapter.deleteOrg(orgId);
|
|
207
|
-
reply.send({
|
|
208
|
-
success: true,
|
|
209
|
-
message: "Organization deleted"
|
|
210
|
-
});
|
|
211
|
-
});
|
|
212
|
-
/**
|
|
213
|
-
* GET /:orgId/members -- List members
|
|
214
|
-
*/
|
|
215
|
-
fastify.get(`${basePath}/:orgId/members`, { preHandler: withAuth(fastify.requireOrgRole([
|
|
216
|
-
"owner",
|
|
217
|
-
"admin",
|
|
218
|
-
"member"
|
|
219
|
-
])) }, async (request, reply) => {
|
|
220
|
-
const { orgId } = request.params;
|
|
221
|
-
const members = await adapter.listMembers(orgId);
|
|
222
|
-
reply.send({
|
|
223
|
-
success: true,
|
|
224
|
-
data: members
|
|
225
|
-
});
|
|
226
|
-
});
|
|
227
|
-
/**
|
|
228
|
-
* POST /:orgId/members -- Add a member
|
|
229
|
-
*
|
|
230
|
-
* Body: { userId: string; role: string }
|
|
231
|
-
*/
|
|
232
|
-
fastify.post(`${basePath}/:orgId/members`, { preHandler: withAuth(fastify.requireOrgRole(["owner", "admin"])) }, async (request, reply) => {
|
|
233
|
-
const { orgId } = request.params;
|
|
234
|
-
const body = request.body;
|
|
235
|
-
if (!body?.userId || !body.role) {
|
|
236
|
-
sendError(reply, 400, "VALIDATION_ERROR", "userId and role are required");
|
|
237
|
-
return;
|
|
238
|
-
}
|
|
239
|
-
if (!validRoleNames.has(body.role)) {
|
|
240
|
-
sendError(reply, 400, "INVALID_ROLE", `Invalid role '${body.role}'. Valid roles: ${[...validRoleNames].join(", ")}`);
|
|
241
|
-
return;
|
|
242
|
-
}
|
|
243
|
-
if (await adapter.getMember(orgId, body.userId)) {
|
|
244
|
-
sendError(reply, 409, "ALREADY_MEMBER", "User is already a member of this organization");
|
|
245
|
-
return;
|
|
246
|
-
}
|
|
247
|
-
const member = await adapter.addMember(orgId, body.userId, body.role);
|
|
248
|
-
reply.code(201).send({
|
|
249
|
-
success: true,
|
|
250
|
-
data: member
|
|
251
|
-
});
|
|
252
|
-
});
|
|
253
|
-
/**
|
|
254
|
-
* PATCH /:orgId/members/:userId -- Update a member's role
|
|
255
|
-
*
|
|
256
|
-
* Body: { role: string }
|
|
257
|
-
*/
|
|
258
|
-
fastify.patch(`${basePath}/:orgId/members/:userId`, { preHandler: withAuth(fastify.requireOrgRole(["owner", "admin"])) }, async (request, reply) => {
|
|
259
|
-
const { orgId, userId } = request.params;
|
|
260
|
-
const body = request.body;
|
|
261
|
-
if (!body?.role) {
|
|
262
|
-
sendError(reply, 400, "VALIDATION_ERROR", "role is required");
|
|
263
|
-
return;
|
|
264
|
-
}
|
|
265
|
-
if (!validRoleNames.has(body.role)) {
|
|
266
|
-
sendError(reply, 400, "INVALID_ROLE", `Invalid role '${body.role}'. Valid roles: ${[...validRoleNames].join(", ")}`);
|
|
267
|
-
return;
|
|
268
|
-
}
|
|
269
|
-
const currentMember = await adapter.getMember(orgId, userId);
|
|
270
|
-
if (!currentMember) {
|
|
271
|
-
sendError(reply, 404, "NOT_FOUND", "Member not found");
|
|
272
|
-
return;
|
|
273
|
-
}
|
|
274
|
-
if (currentMember.role === "owner" && body.role !== "owner") {
|
|
275
|
-
if ((await adapter.listMembers(orgId)).filter((m) => m.role === "owner").length <= 1) {
|
|
276
|
-
sendError(reply, 400, "LAST_OWNER", "Cannot change the role of the last owner. Transfer ownership first.");
|
|
277
|
-
return;
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
const member = await adapter.updateMemberRole(orgId, userId, body.role);
|
|
281
|
-
if (!member) {
|
|
282
|
-
sendError(reply, 404, "NOT_FOUND", "Member not found");
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
285
|
-
reply.send({
|
|
286
|
-
success: true,
|
|
287
|
-
data: member
|
|
288
|
-
});
|
|
289
|
-
});
|
|
290
|
-
/**
|
|
291
|
-
* DELETE /:orgId/members/:userId -- Remove a member
|
|
292
|
-
*/
|
|
293
|
-
fastify.delete(`${basePath}/:orgId/members/:userId`, { preHandler: withAuth(fastify.requireOrgRole(["owner", "admin"])) }, async (request, reply) => {
|
|
294
|
-
const { orgId, userId } = request.params;
|
|
295
|
-
const member = await adapter.getMember(orgId, userId);
|
|
296
|
-
if (!member) {
|
|
297
|
-
sendError(reply, 404, "NOT_FOUND", "Member not found");
|
|
298
|
-
return;
|
|
299
|
-
}
|
|
300
|
-
if (member.role === "owner") {
|
|
301
|
-
if ((await adapter.listMembers(orgId)).filter((m) => m.role === "owner").length <= 1) {
|
|
302
|
-
sendError(reply, 400, "LAST_OWNER", "Cannot remove the last owner. Transfer ownership or delete the organization.");
|
|
303
|
-
return;
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
await adapter.removeMember(orgId, userId);
|
|
307
|
-
reply.send({
|
|
308
|
-
success: true,
|
|
309
|
-
message: "Member removed"
|
|
310
|
-
});
|
|
311
|
-
});
|
|
312
|
-
if (enableInvitations && adapter.invitations) {
|
|
313
|
-
const inv = adapter.invitations;
|
|
314
|
-
/**
|
|
315
|
-
* POST /:orgId/invitations -- Create invitation
|
|
316
|
-
*
|
|
317
|
-
* Body: { email: string; role: string; expiresAt?: string }
|
|
318
|
-
*/
|
|
319
|
-
fastify.post(`${basePath}/:orgId/invitations`, { preHandler: withAuth(fastify.requireOrgRole(["owner", "admin"])) }, async (request, reply) => {
|
|
320
|
-
const user = getUser(request);
|
|
321
|
-
const userId = user ? getUserId(user) : void 0;
|
|
322
|
-
if (!userId) {
|
|
323
|
-
sendError(reply, 401, "UNAUTHORIZED", "Authentication required");
|
|
324
|
-
return;
|
|
325
|
-
}
|
|
326
|
-
const { orgId } = request.params;
|
|
327
|
-
const body = request.body;
|
|
328
|
-
if (!body?.email || !body.role) {
|
|
329
|
-
sendError(reply, 400, "VALIDATION_ERROR", "email and role are required");
|
|
330
|
-
return;
|
|
331
|
-
}
|
|
332
|
-
if (!validRoleNames.has(body.role)) {
|
|
333
|
-
sendError(reply, 400, "INVALID_ROLE", `Invalid role '${body.role}'. Valid roles: ${[...validRoleNames].join(", ")}`);
|
|
334
|
-
return;
|
|
335
|
-
}
|
|
336
|
-
const defaultExpiry = new Date(Date.now() + 10080 * 60 * 1e3);
|
|
337
|
-
const expiresAt = body.expiresAt ? new Date(body.expiresAt) : defaultExpiry;
|
|
338
|
-
const invitation = await inv.create({
|
|
339
|
-
orgId,
|
|
340
|
-
email: body.email,
|
|
341
|
-
role: body.role,
|
|
342
|
-
invitedBy: userId,
|
|
343
|
-
status: "pending",
|
|
344
|
-
expiresAt
|
|
345
|
-
});
|
|
346
|
-
reply.code(201).send({
|
|
347
|
-
success: true,
|
|
348
|
-
data: invitation
|
|
349
|
-
});
|
|
350
|
-
});
|
|
351
|
-
/**
|
|
352
|
-
* GET /:orgId/invitations -- List pending invitations
|
|
353
|
-
*/
|
|
354
|
-
fastify.get(`${basePath}/:orgId/invitations`, { preHandler: withAuth(fastify.requireOrgRole(["owner", "admin"])) }, async (request, reply) => {
|
|
355
|
-
const { orgId } = request.params;
|
|
356
|
-
const invitations = await inv.listPending(orgId);
|
|
357
|
-
reply.send({
|
|
358
|
-
success: true,
|
|
359
|
-
data: invitations
|
|
360
|
-
});
|
|
361
|
-
});
|
|
362
|
-
/**
|
|
363
|
-
* POST /invitations/:invitationId/accept -- Accept invitation
|
|
364
|
-
*/
|
|
365
|
-
fastify.post(`${basePath}/invitations/:invitationId/accept`, { preHandler: withAuth() }, async (request, reply) => {
|
|
366
|
-
const { invitationId } = request.params;
|
|
367
|
-
await inv.accept(invitationId);
|
|
368
|
-
reply.send({
|
|
369
|
-
success: true,
|
|
370
|
-
message: "Invitation accepted"
|
|
371
|
-
});
|
|
372
|
-
});
|
|
373
|
-
/**
|
|
374
|
-
* POST /invitations/:invitationId/reject -- Reject invitation
|
|
375
|
-
*/
|
|
376
|
-
fastify.post(`${basePath}/invitations/:invitationId/reject`, { preHandler: withAuth() }, async (request, reply) => {
|
|
377
|
-
const { invitationId } = request.params;
|
|
378
|
-
await inv.reject(invitationId);
|
|
379
|
-
reply.send({
|
|
380
|
-
success: true,
|
|
381
|
-
message: "Invitation rejected"
|
|
382
|
-
});
|
|
383
|
-
});
|
|
384
|
-
}
|
|
385
|
-
fastify.log?.debug?.({
|
|
386
|
-
basePath,
|
|
387
|
-
roles: [...validRoleNames],
|
|
388
|
-
invitations: enableInvitations
|
|
389
|
-
}, "Organization plugin registered");
|
|
390
|
-
};
|
|
391
|
-
var organizationPlugin_default = fp(organizationPlugin, {
|
|
392
|
-
name: "arc-organization",
|
|
393
|
-
fastify: "5.x"
|
|
394
|
-
});
|
|
395
|
-
//#endregion
|
|
396
|
-
//#region src/org/orgGuard.ts
|
|
397
|
-
/**
|
|
398
|
-
* Create org guard middleware.
|
|
399
|
-
* Reads `request.scope` for org context and roles.
|
|
400
|
-
* Elevated scope always passes.
|
|
401
|
-
*/
|
|
402
|
-
function orgGuard(options = {}) {
|
|
403
|
-
const { requireOrgContext = true, roles = [] } = options;
|
|
404
|
-
return async function orgGuardMiddleware(request, reply) {
|
|
405
|
-
const scope = request.scope ?? PUBLIC_SCOPE;
|
|
406
|
-
if (isElevated(scope)) return;
|
|
407
|
-
if (requireOrgContext && !hasOrgAccess(scope)) {
|
|
408
|
-
reply.code(403).send({
|
|
409
|
-
success: false,
|
|
410
|
-
error: "Organization context required",
|
|
411
|
-
code: "ORG_CONTEXT_REQUIRED",
|
|
412
|
-
message: "This endpoint requires an organization context. Please specify organization via x-organization-id header."
|
|
413
|
-
});
|
|
414
|
-
return;
|
|
415
|
-
}
|
|
416
|
-
if (roles.length > 0 && isMember(scope)) {
|
|
417
|
-
const userOrgRoles = getOrgRoles(scope);
|
|
418
|
-
if (!roles.some((role) => userOrgRoles.includes(role))) {
|
|
419
|
-
reply.code(403).send({
|
|
420
|
-
success: false,
|
|
421
|
-
error: "Insufficient organization permissions",
|
|
422
|
-
code: "ORG_ROLE_REQUIRED",
|
|
423
|
-
message: `This action requires one of these organization roles: ${roles.join(", ")}`,
|
|
424
|
-
required: roles,
|
|
425
|
-
current: userOrgRoles
|
|
426
|
-
});
|
|
427
|
-
return;
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
};
|
|
431
|
-
}
|
|
432
|
-
/**
|
|
433
|
-
* Shorthand for requiring org context
|
|
434
|
-
*/
|
|
435
|
-
function requireOrg() {
|
|
436
|
-
return orgGuard({ requireOrgContext: true });
|
|
437
|
-
}
|
|
438
|
-
/**
|
|
439
|
-
* Require org context with specific roles
|
|
440
|
-
*/
|
|
441
|
-
function requireOrgRole(...roles) {
|
|
442
|
-
return orgGuard({
|
|
443
|
-
requireOrgContext: true,
|
|
444
|
-
roles
|
|
445
|
-
});
|
|
446
|
-
}
|
|
447
|
-
//#endregion
|
|
448
|
-
//#region src/org/orgMembership.ts
|
|
449
|
-
/**
|
|
450
|
-
* Check if user is member of organization.
|
|
451
|
-
* This is a low-level utility for checking membership from user object data.
|
|
452
|
-
* For request-level checks, use `request.scope` (isMember/isElevated guards).
|
|
453
|
-
*/
|
|
454
|
-
async function orgMembershipCheck(user, orgId, options = {}) {
|
|
455
|
-
const { userOrgsPath = "organizations", validateFromDb } = options;
|
|
456
|
-
if (!user || !orgId) return false;
|
|
457
|
-
if ((user[userOrgsPath] ?? []).some((o) => {
|
|
458
|
-
return (o.organizationId?.toString() ?? String(o)) === orgId.toString();
|
|
459
|
-
})) return true;
|
|
460
|
-
if (validateFromDb) {
|
|
461
|
-
const userId = (user._id ?? user.id)?.toString();
|
|
462
|
-
if (userId) return validateFromDb(userId, orgId);
|
|
463
|
-
}
|
|
464
|
-
return false;
|
|
465
|
-
}
|
|
466
|
-
/**
|
|
467
|
-
* Get user's role in organization from user object data.
|
|
468
|
-
* For request-level role checks, use `request.scope.orgRoles` (when scope is 'member').
|
|
469
|
-
*/
|
|
470
|
-
function getUserOrgRoles(user, orgId, options = {}) {
|
|
471
|
-
const { userOrgsPath = "organizations" } = options;
|
|
472
|
-
if (!user || !orgId) return [];
|
|
473
|
-
return (user[userOrgsPath] ?? []).find((o) => {
|
|
474
|
-
return (o.organizationId?.toString() ?? String(o)) === orgId.toString();
|
|
475
|
-
})?.roles ?? [];
|
|
476
|
-
}
|
|
477
|
-
/**
|
|
478
|
-
* Check if user has specific role in organization from user object data.
|
|
479
|
-
* For request-level role checks, use `requireOrgRole()` permission or `request.scope`.
|
|
480
|
-
*/
|
|
481
|
-
function hasOrgRole(user, orgId, roles, options = {}) {
|
|
482
|
-
const userOrgRoles = getUserOrgRoles(user, orgId, options);
|
|
483
|
-
return (Array.isArray(roles) ? roles : [roles]).some((role) => userOrgRoles.includes(role));
|
|
484
|
-
}
|
|
485
|
-
//#endregion
|
|
486
|
-
export { getUserOrgRoles, hasOrgRole, orgGuard, orgMembershipCheck, organizationPlugin_default as organizationPlugin, organizationPlugin as organizationPluginFn, requireOrg, requireOrgRole };
|
package/dist/org/types.d.mts
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
//#region src/org/types.d.ts
|
|
2
|
-
/**
|
|
3
|
-
* Organization types -- adapter interfaces for multi-tenant applications.
|
|
4
|
-
* Arc defines the contract, apps implement it.
|
|
5
|
-
*/
|
|
6
|
-
interface OrgDoc {
|
|
7
|
-
id: string;
|
|
8
|
-
name: string;
|
|
9
|
-
slug: string;
|
|
10
|
-
ownerId: string;
|
|
11
|
-
metadata?: Record<string, unknown>;
|
|
12
|
-
createdAt?: Date;
|
|
13
|
-
updatedAt?: Date;
|
|
14
|
-
[key: string]: unknown;
|
|
15
|
-
}
|
|
16
|
-
interface MemberDoc {
|
|
17
|
-
id: string;
|
|
18
|
-
orgId: string;
|
|
19
|
-
userId: string;
|
|
20
|
-
role: string;
|
|
21
|
-
createdAt?: Date;
|
|
22
|
-
updatedAt?: Date;
|
|
23
|
-
[key: string]: unknown;
|
|
24
|
-
}
|
|
25
|
-
interface InvitationDoc {
|
|
26
|
-
id: string;
|
|
27
|
-
orgId: string;
|
|
28
|
-
email: string;
|
|
29
|
-
role: string;
|
|
30
|
-
invitedBy: string;
|
|
31
|
-
status: "pending" | "accepted" | "rejected" | "expired";
|
|
32
|
-
expiresAt: Date;
|
|
33
|
-
createdAt?: Date;
|
|
34
|
-
}
|
|
35
|
-
/** Core organization adapter -- apps implement this */
|
|
36
|
-
interface OrgAdapter {
|
|
37
|
-
createOrg(data: {
|
|
38
|
-
name: string;
|
|
39
|
-
slug: string;
|
|
40
|
-
ownerId: string;
|
|
41
|
-
[key: string]: unknown;
|
|
42
|
-
}): Promise<OrgDoc>;
|
|
43
|
-
getOrg(id: string): Promise<OrgDoc | null>;
|
|
44
|
-
getOrgBySlug(slug: string): Promise<OrgDoc | null>;
|
|
45
|
-
updateOrg(id: string, data: Partial<OrgDoc>): Promise<OrgDoc | null>;
|
|
46
|
-
deleteOrg(id: string): Promise<void>;
|
|
47
|
-
listUserOrgs(userId: string): Promise<OrgDoc[]>;
|
|
48
|
-
addMember(orgId: string, userId: string, role: string): Promise<MemberDoc>;
|
|
49
|
-
removeMember(orgId: string, userId: string): Promise<void>;
|
|
50
|
-
getMember(orgId: string, userId: string): Promise<MemberDoc | null>;
|
|
51
|
-
listMembers(orgId: string): Promise<MemberDoc[]>;
|
|
52
|
-
updateMemberRole(orgId: string, userId: string, role: string): Promise<MemberDoc | null>;
|
|
53
|
-
invitations?: InvitationAdapter;
|
|
54
|
-
}
|
|
55
|
-
interface InvitationAdapter {
|
|
56
|
-
create(data: Omit<InvitationDoc, "id" | "createdAt">): Promise<InvitationDoc>;
|
|
57
|
-
getByToken(token: string): Promise<InvitationDoc | null>;
|
|
58
|
-
accept(id: string): Promise<void>;
|
|
59
|
-
reject(id: string): Promise<void>;
|
|
60
|
-
listPending(orgId: string): Promise<InvitationDoc[]>;
|
|
61
|
-
}
|
|
62
|
-
/** Statement-based permission check */
|
|
63
|
-
interface OrgPermissionStatement {
|
|
64
|
-
resource: string;
|
|
65
|
-
action: string[];
|
|
66
|
-
}
|
|
67
|
-
/** Role definition with permissions */
|
|
68
|
-
interface OrgRole {
|
|
69
|
-
name: string;
|
|
70
|
-
permissions: OrgPermissionStatement[];
|
|
71
|
-
}
|
|
72
|
-
interface OrganizationPluginOptions {
|
|
73
|
-
adapter: OrgAdapter;
|
|
74
|
-
/** Built-in roles (default: owner, admin, member) */
|
|
75
|
-
roles?: OrgRole[];
|
|
76
|
-
/** Base path for org API routes (default: '/api/organizations') */
|
|
77
|
-
basePath?: string;
|
|
78
|
-
/** Enable invitation system */
|
|
79
|
-
enableInvitations?: boolean;
|
|
80
|
-
}
|
|
81
|
-
//#endregion
|
|
82
|
-
export { InvitationAdapter, InvitationDoc, MemberDoc, OrgAdapter, OrgDoc, OrgPermissionStatement, OrgRole, OrganizationPluginOptions };
|
package/dist/org/types.mjs
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import fp from "fastify-plugin";
|
|
2
|
-
//#region src/registry/introspectionPlugin.ts
|
|
3
|
-
const introspectionPlugin = async (fastify, opts = {}) => {
|
|
4
|
-
const { prefix = "/_resources", authRoles = ["superadmin"], enabled = true } = opts;
|
|
5
|
-
if (!enabled) {
|
|
6
|
-
fastify.log?.debug?.("Introspection plugin disabled");
|
|
7
|
-
return;
|
|
8
|
-
}
|
|
9
|
-
const typedFastify = fastify;
|
|
10
|
-
const authMiddleware = authRoles.length > 0 && typedFastify.authenticate ? [typedFastify.authenticate, typedFastify.authorize?.(...authRoles)].filter(Boolean) : [];
|
|
11
|
-
const getRegistry = () => fastify.arc?.registry;
|
|
12
|
-
await fastify.register(async (instance) => {
|
|
13
|
-
instance.get("/", { preHandler: authMiddleware }, async (_req, _reply) => {
|
|
14
|
-
return getRegistry()?.getIntrospection() ?? {
|
|
15
|
-
resources: [],
|
|
16
|
-
stats: {},
|
|
17
|
-
generatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
18
|
-
};
|
|
19
|
-
});
|
|
20
|
-
instance.get("/stats", { preHandler: authMiddleware }, async (_req, _reply) => {
|
|
21
|
-
return getRegistry()?.getStats() ?? {
|
|
22
|
-
totalResources: 0,
|
|
23
|
-
byModule: {},
|
|
24
|
-
presetUsage: {},
|
|
25
|
-
totalRoutes: 0,
|
|
26
|
-
totalEvents: 0
|
|
27
|
-
};
|
|
28
|
-
});
|
|
29
|
-
instance.get("/:name", {
|
|
30
|
-
schema: { params: {
|
|
31
|
-
type: "object",
|
|
32
|
-
properties: { name: { type: "string" } },
|
|
33
|
-
required: ["name"]
|
|
34
|
-
} },
|
|
35
|
-
preHandler: authMiddleware
|
|
36
|
-
}, async (req, reply) => {
|
|
37
|
-
const resource = getRegistry()?.get(req.params.name);
|
|
38
|
-
if (!resource) return reply.code(404).send({ error: `Resource '${req.params.name}' not found` });
|
|
39
|
-
return resource;
|
|
40
|
-
});
|
|
41
|
-
}, { prefix });
|
|
42
|
-
fastify.log?.debug?.(`Introspection API at ${prefix}`);
|
|
43
|
-
};
|
|
44
|
-
var introspectionPlugin_default = fp(introspectionPlugin, { name: "arc-introspection" });
|
|
45
|
-
//#endregion
|
|
46
|
-
export { introspectionPlugin_default as n, introspectionPlugin as t };
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|