@classytic/arc 1.1.0 → 2.1.3
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 +247 -794
- package/bin/arc.js +91 -52
- package/dist/EventTransport-BkUDYZEb.d.mts +99 -0
- package/dist/HookSystem-BsGV-j2l.mjs +404 -0
- package/dist/ResourceRegistry-7Ic20ZMw.mjs +249 -0
- package/dist/adapters/index.d.mts +5 -0
- package/dist/adapters/index.mjs +3 -0
- package/dist/audit/index.d.mts +81 -0
- package/dist/audit/index.mjs +275 -0
- package/dist/audit/mongodb.d.mts +5 -0
- package/dist/audit/mongodb.mjs +3 -0
- package/dist/audited-CGdLiSlE.mjs +140 -0
- package/dist/auth/index.d.mts +188 -0
- package/dist/auth/index.mjs +1096 -0
- package/dist/auth/redis-session.d.mts +43 -0
- package/dist/auth/redis-session.mjs +75 -0
- package/dist/betterAuthOpenApi-DjWDddNc.mjs +249 -0
- package/dist/cache/index.d.mts +145 -0
- package/dist/cache/index.mjs +91 -0
- package/dist/caching-GSDJcA6-.mjs +93 -0
- package/dist/chunk-C7Uep-_p.mjs +20 -0
- package/dist/circuitBreaker-DYhWBW_D.mjs +1096 -0
- package/dist/cli/commands/describe.d.mts +18 -0
- package/dist/cli/commands/describe.mjs +238 -0
- package/dist/cli/commands/docs.d.mts +13 -0
- package/dist/cli/commands/docs.mjs +52 -0
- package/dist/cli/commands/{generate.d.ts → generate.d.mts} +3 -2
- package/dist/cli/commands/generate.mjs +357 -0
- package/dist/cli/commands/{init.d.ts → init.d.mts} +11 -8
- package/dist/cli/commands/{init.js → init.mjs} +807 -617
- package/dist/cli/commands/introspect.d.mts +10 -0
- package/dist/cli/commands/introspect.mjs +75 -0
- package/dist/cli/index.d.mts +16 -0
- package/dist/cli/index.mjs +156 -0
- package/dist/constants-DdXFXQtN.mjs +84 -0
- package/dist/core/index.d.mts +5 -0
- package/dist/core/index.mjs +4 -0
- package/dist/createApp-D2D5XXaV.mjs +559 -0
- package/dist/defineResource-PXzSJ15_.mjs +2197 -0
- package/dist/discovery/index.d.mts +46 -0
- package/dist/discovery/index.mjs +109 -0
- package/dist/docs/index.d.mts +162 -0
- package/dist/docs/index.mjs +74 -0
- package/dist/elevation-DGo5shaX.d.mts +87 -0
- package/dist/elevation-DSTbVvYj.mjs +113 -0
- package/dist/errorHandler-C3GY3_ow.mjs +108 -0
- package/dist/errorHandler-CW3OOeYq.d.mts +72 -0
- package/dist/errors-DAWRdiYP.d.mts +124 -0
- package/dist/errors-DBANPbGr.mjs +211 -0
- package/dist/eventPlugin-BEOvaDqo.mjs +229 -0
- package/dist/eventPlugin-H6wDDjGO.d.mts +124 -0
- package/dist/events/index.d.mts +53 -0
- package/dist/events/index.mjs +51 -0
- package/dist/events/transports/redis-stream-entry.d.mts +2 -0
- package/dist/events/transports/redis-stream-entry.mjs +177 -0
- package/dist/events/transports/redis.d.mts +76 -0
- package/dist/events/transports/redis.mjs +124 -0
- package/dist/externalPaths-SyPF2tgK.d.mts +50 -0
- package/dist/factory/index.d.mts +63 -0
- package/dist/factory/index.mjs +3 -0
- package/dist/fastifyAdapter-C8DlE0YH.d.mts +216 -0
- package/dist/fields-Bi_AVKSo.d.mts +109 -0
- package/dist/fields-CTd_CrKr.mjs +114 -0
- package/dist/hooks/index.d.mts +4 -0
- package/dist/hooks/index.mjs +3 -0
- package/dist/idempotency/index.d.mts +96 -0
- package/dist/idempotency/index.mjs +319 -0
- package/dist/idempotency/mongodb.d.mts +2 -0
- package/dist/idempotency/mongodb.mjs +114 -0
- package/dist/idempotency/redis.d.mts +2 -0
- package/dist/idempotency/redis.mjs +103 -0
- package/dist/index.d.mts +260 -0
- package/dist/index.mjs +104 -0
- package/dist/integrations/event-gateway.d.mts +46 -0
- package/dist/integrations/event-gateway.mjs +43 -0
- package/dist/integrations/index.d.mts +5 -0
- package/dist/integrations/index.mjs +1 -0
- package/dist/integrations/jobs.d.mts +103 -0
- package/dist/integrations/jobs.mjs +123 -0
- package/dist/integrations/streamline.d.mts +60 -0
- package/dist/integrations/streamline.mjs +125 -0
- package/dist/integrations/websocket.d.mts +82 -0
- package/dist/integrations/websocket.mjs +288 -0
- package/dist/interface-CSNjltAc.d.mts +77 -0
- package/dist/interface-DTbsvIWe.d.mts +54 -0
- package/dist/interface-e9XfSsUV.d.mts +1097 -0
- package/dist/introspectionPlugin-B3JkrjwU.mjs +53 -0
- package/dist/keys-DhqDRxv3.mjs +42 -0
- package/dist/logger-ByrvQWZO.mjs +78 -0
- package/dist/memory-B2v7KrCB.mjs +143 -0
- package/dist/migrations/index.d.mts +156 -0
- package/dist/migrations/index.mjs +260 -0
- package/dist/mongodb-ClykrfGo.d.mts +118 -0
- package/dist/mongodb-DNKEExbf.mjs +93 -0
- package/dist/mongodb-Dg8O_gvd.d.mts +71 -0
- package/dist/openapi-9nB_kiuR.mjs +525 -0
- package/dist/org/index.d.mts +68 -0
- package/dist/org/index.mjs +513 -0
- package/dist/org/types.d.mts +82 -0
- package/dist/org/types.mjs +1 -0
- package/dist/permissions/index.d.mts +278 -0
- package/dist/permissions/index.mjs +579 -0
- package/dist/plugins/index.d.mts +172 -0
- package/dist/plugins/index.mjs +522 -0
- package/dist/plugins/response-cache.d.mts +87 -0
- package/dist/plugins/response-cache.mjs +283 -0
- package/dist/plugins/tracing-entry.d.mts +2 -0
- package/dist/plugins/tracing-entry.mjs +185 -0
- package/dist/pluralize-CM-jZg7p.mjs +86 -0
- package/dist/policies/{index.d.ts → index.d.mts} +204 -170
- package/dist/policies/index.mjs +321 -0
- package/dist/presets/{index.d.ts → index.d.mts} +62 -131
- package/dist/presets/index.mjs +143 -0
- package/dist/presets/multiTenant.d.mts +24 -0
- package/dist/presets/multiTenant.mjs +113 -0
- package/dist/presets-BTeYbw7h.d.mts +57 -0
- package/dist/presets-CeFtfDR8.mjs +119 -0
- package/dist/prisma-C3iornoK.d.mts +274 -0
- package/dist/prisma-DJbMt3yf.mjs +627 -0
- package/dist/queryCachePlugin-B6R0d4av.mjs +138 -0
- package/dist/queryCachePlugin-Q6SYuHZ6.d.mts +71 -0
- package/dist/redis-UwjEp8Ea.d.mts +49 -0
- package/dist/redis-stream-CBg0upHI.d.mts +103 -0
- package/dist/registry/index.d.mts +11 -0
- package/dist/registry/index.mjs +4 -0
- package/dist/requestContext-xi6OKBL-.mjs +55 -0
- package/dist/schemaConverter-Dtg0Kt9T.mjs +98 -0
- package/dist/schemas/index.d.mts +63 -0
- package/dist/schemas/index.mjs +82 -0
- package/dist/scope/index.d.mts +21 -0
- package/dist/scope/index.mjs +65 -0
- package/dist/sessionManager-D_iEHjQl.d.mts +186 -0
- package/dist/sse-DkqQ1uxb.mjs +123 -0
- package/dist/testing/index.d.mts +907 -0
- package/dist/testing/index.mjs +1976 -0
- package/dist/tracing-8CEbhF0w.d.mts +70 -0
- package/dist/typeGuards-DwxA1t_L.mjs +9 -0
- package/dist/types/index.d.mts +946 -0
- package/dist/types/index.mjs +14 -0
- package/dist/types-B0dhNrnd.d.mts +445 -0
- package/dist/types-Beqn1Un7.mjs +38 -0
- package/dist/types-DelU6kln.mjs +25 -0
- package/dist/types-RLkFVgaw.d.mts +101 -0
- package/dist/utils/index.d.mts +747 -0
- package/dist/utils/index.mjs +6 -0
- package/package.json +194 -68
- package/dist/BaseController-DVAiHxEQ.d.ts +0 -233
- package/dist/adapters/index.d.ts +0 -237
- package/dist/adapters/index.js +0 -668
- package/dist/arcCorePlugin-CsShQdyP.d.ts +0 -273
- package/dist/audit/index.d.ts +0 -195
- package/dist/audit/index.js +0 -319
- package/dist/auth/index.d.ts +0 -47
- package/dist/auth/index.js +0 -174
- package/dist/cli/commands/docs.d.ts +0 -11
- package/dist/cli/commands/docs.js +0 -474
- package/dist/cli/commands/generate.js +0 -334
- package/dist/cli/commands/introspect.d.ts +0 -8
- package/dist/cli/commands/introspect.js +0 -338
- package/dist/cli/index.d.ts +0 -4
- package/dist/cli/index.js +0 -3269
- package/dist/core/index.d.ts +0 -220
- package/dist/core/index.js +0 -2786
- package/dist/createApp-Ce9wl8W9.d.ts +0 -77
- package/dist/docs/index.d.ts +0 -166
- package/dist/docs/index.js +0 -658
- package/dist/errors-8WIxGS_6.d.ts +0 -122
- package/dist/events/index.d.ts +0 -117
- package/dist/events/index.js +0 -89
- package/dist/factory/index.d.ts +0 -38
- package/dist/factory/index.js +0 -1652
- package/dist/hooks/index.d.ts +0 -4
- package/dist/hooks/index.js +0 -199
- package/dist/idempotency/index.d.ts +0 -323
- package/dist/idempotency/index.js +0 -500
- package/dist/index-B4t03KQ0.d.ts +0 -1366
- package/dist/index.d.ts +0 -135
- package/dist/index.js +0 -4756
- package/dist/migrations/index.d.ts +0 -185
- package/dist/migrations/index.js +0 -274
- package/dist/org/index.d.ts +0 -129
- package/dist/org/index.js +0 -220
- package/dist/permissions/index.d.ts +0 -144
- package/dist/permissions/index.js +0 -103
- package/dist/plugins/index.d.ts +0 -46
- package/dist/plugins/index.js +0 -1069
- package/dist/policies/index.js +0 -196
- package/dist/presets/index.js +0 -384
- package/dist/presets/multiTenant.d.ts +0 -39
- package/dist/presets/multiTenant.js +0 -112
- package/dist/registry/index.d.ts +0 -16
- package/dist/registry/index.js +0 -253
- package/dist/testing/index.d.ts +0 -618
- package/dist/testing/index.js +0 -48020
- package/dist/types/index.d.ts +0 -4
- package/dist/types/index.js +0 -8
- package/dist/types-B99TBmFV.d.ts +0 -76
- package/dist/types-BvckRbs2.d.ts +0 -143
- package/dist/utils/index.d.ts +0 -679
- package/dist/utils/index.js +0 -931
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { FastifyPluginAsync } from "fastify";
|
|
2
|
+
|
|
3
|
+
//#region src/discovery/index.d.ts
|
|
4
|
+
/** A discovered resource — must have toPlugin() method */
|
|
5
|
+
interface DiscoverableResource {
|
|
6
|
+
toPlugin(): FastifyPluginAsync;
|
|
7
|
+
name?: string;
|
|
8
|
+
definition?: {
|
|
9
|
+
name: string;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
interface DiscoveryOptions {
|
|
13
|
+
/** Directories to scan (relative to cwd or absolute) */
|
|
14
|
+
paths: string[];
|
|
15
|
+
/**
|
|
16
|
+
* File name pattern to match.
|
|
17
|
+
* Supports simple globs: *.resource.ts, *.resource.js
|
|
18
|
+
* Default: '*.resource.{ts,js}'
|
|
19
|
+
*/
|
|
20
|
+
pattern?: string;
|
|
21
|
+
/** Export name to look for in each file (default: 'default' then first ResourceDefinition) */
|
|
22
|
+
exportName?: string;
|
|
23
|
+
/** Filter function to include/exclude discovered resources */
|
|
24
|
+
filter?: (resource: DiscoverableResource, filePath: string) => boolean;
|
|
25
|
+
/** Called for each discovered resource (for logging) */
|
|
26
|
+
onDiscover?: (name: string, filePath: string) => void;
|
|
27
|
+
/** Whether to scan recursively (default: true) */
|
|
28
|
+
recursive?: boolean;
|
|
29
|
+
}
|
|
30
|
+
interface DiscoveryPluginOptions extends DiscoveryOptions {
|
|
31
|
+
/** URL prefix applied to all discovered resources */
|
|
32
|
+
prefix?: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Discover and import resource files.
|
|
36
|
+
*
|
|
37
|
+
* @returns Array of discovered resources with their file paths
|
|
38
|
+
*/
|
|
39
|
+
declare function discoverResources(options: DiscoveryOptions): Promise<Array<{
|
|
40
|
+
resource: DiscoverableResource;
|
|
41
|
+
filePath: string;
|
|
42
|
+
}>>;
|
|
43
|
+
/** Auto-discovery plugin for Arc resources */
|
|
44
|
+
declare const discoveryPlugin: FastifyPluginAsync<DiscoveryPluginOptions>;
|
|
45
|
+
//#endregion
|
|
46
|
+
export { DiscoverableResource, DiscoveryOptions, DiscoveryPluginOptions, discoveryPlugin as default, discoveryPlugin, discoverResources };
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { extname, join, resolve } from "node:path";
|
|
2
|
+
import { readdir } from "node:fs/promises";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
|
|
5
|
+
//#region src/discovery/index.ts
|
|
6
|
+
/**
|
|
7
|
+
* Match a filename against a simple pattern.
|
|
8
|
+
* Supports: *.resource.ts, *.resource.js, *.resource.{ts,js}
|
|
9
|
+
*/
|
|
10
|
+
function matchPattern(filename, pattern) {
|
|
11
|
+
if (pattern.includes("{") && pattern.includes("}")) {
|
|
12
|
+
const match = pattern.match(/\{([^}]+)\}/);
|
|
13
|
+
if (match) return match[1].split(",").map((s) => s.trim()).some((alt) => {
|
|
14
|
+
return matchPattern(filename, pattern.replace(match[0], alt));
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
if (pattern.startsWith("*")) {
|
|
18
|
+
const suffix = pattern.slice(1);
|
|
19
|
+
return filename.endsWith(suffix);
|
|
20
|
+
}
|
|
21
|
+
return filename === pattern;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Recursively scan directories for files matching a pattern.
|
|
25
|
+
*/
|
|
26
|
+
async function scanDirectory(dir, pattern, recursive) {
|
|
27
|
+
const results = [];
|
|
28
|
+
const resolvedDir = resolve(dir);
|
|
29
|
+
const entries = await readdir(resolvedDir, { withFileTypes: true }).catch(() => []);
|
|
30
|
+
for (const entry of entries) {
|
|
31
|
+
const fullPath = join(resolvedDir, String(entry.name));
|
|
32
|
+
if (entry.isDirectory() && recursive) {
|
|
33
|
+
const nested = await scanDirectory(fullPath, pattern, recursive);
|
|
34
|
+
results.push(...nested);
|
|
35
|
+
} else if (entry.isFile()) {
|
|
36
|
+
const filePattern = pattern.replace(/^\*\*\//, "");
|
|
37
|
+
if (matchPattern(String(entry.name), filePattern)) results.push(fullPath);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return results;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Discover and import resource files.
|
|
44
|
+
*
|
|
45
|
+
* @returns Array of discovered resources with their file paths
|
|
46
|
+
*/
|
|
47
|
+
async function discoverResources(options) {
|
|
48
|
+
const { paths, pattern = "*.resource.{ts,js}", exportName, filter, onDiscover, recursive = true } = options;
|
|
49
|
+
const discovered = [];
|
|
50
|
+
const allFiles = [];
|
|
51
|
+
for (const dir of paths) {
|
|
52
|
+
const files = await scanDirectory(dir, pattern, recursive);
|
|
53
|
+
allFiles.push(...files);
|
|
54
|
+
}
|
|
55
|
+
allFiles.sort();
|
|
56
|
+
for (const filePath of allFiles) {
|
|
57
|
+
extname(filePath);
|
|
58
|
+
const fileUrl = pathToFileURL(filePath).href;
|
|
59
|
+
let module;
|
|
60
|
+
try {
|
|
61
|
+
module = await import(fileUrl);
|
|
62
|
+
} catch (err) {
|
|
63
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
64
|
+
throw new Error(`Failed to import resource file: ${filePath}\n${message}`);
|
|
65
|
+
}
|
|
66
|
+
let resource = null;
|
|
67
|
+
if (exportName && module[exportName]) resource = module[exportName];
|
|
68
|
+
if (!resource && module.default && typeof module.default.toPlugin === "function") resource = module.default;
|
|
69
|
+
if (!resource) {
|
|
70
|
+
for (const value of Object.values(module)) if (value && typeof value === "object" && typeof value.toPlugin === "function") {
|
|
71
|
+
resource = value;
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (!resource) throw new Error(`No resource found in: ${filePath}\nResource files must export an object with a toPlugin() method.
|
|
76
|
+
Use defineResource() or export the resource as default.`);
|
|
77
|
+
if (filter && !filter(resource, filePath)) continue;
|
|
78
|
+
const name = resource.definition?.name ?? resource.name ?? filePath;
|
|
79
|
+
onDiscover?.(name, filePath);
|
|
80
|
+
discovered.push({
|
|
81
|
+
resource,
|
|
82
|
+
filePath
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
return discovered;
|
|
86
|
+
}
|
|
87
|
+
const discoveryPluginImpl = async (fastify, options) => {
|
|
88
|
+
const { prefix, ...discoveryOptions } = options;
|
|
89
|
+
const discovered = await discoverResources({
|
|
90
|
+
...discoveryOptions,
|
|
91
|
+
onDiscover: discoveryOptions.onDiscover ?? ((name, filePath) => {
|
|
92
|
+
fastify.log.debug({
|
|
93
|
+
resource: name,
|
|
94
|
+
file: filePath
|
|
95
|
+
}, "Auto-discovered resource");
|
|
96
|
+
})
|
|
97
|
+
});
|
|
98
|
+
for (const { resource } of discovered) {
|
|
99
|
+
const plugin = resource.toPlugin();
|
|
100
|
+
if (prefix) await fastify.register(plugin, { prefix });
|
|
101
|
+
else await fastify.register(plugin);
|
|
102
|
+
}
|
|
103
|
+
fastify.log.debug(`Auto-discovery: registered ${discovered.length} resource(s)`);
|
|
104
|
+
};
|
|
105
|
+
/** Auto-discovery plugin for Arc resources */
|
|
106
|
+
const discoveryPlugin = discoveryPluginImpl;
|
|
107
|
+
|
|
108
|
+
//#endregion
|
|
109
|
+
export { discoveryPlugin as default, discoveryPlugin, discoverResources };
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import "../elevation-DGo5shaX.mjs";
|
|
2
|
+
import "../interface-e9XfSsUV.mjs";
|
|
3
|
+
import "../types-RLkFVgaw.mjs";
|
|
4
|
+
import { RegistryEntry } from "../types/index.mjs";
|
|
5
|
+
import { t as ExternalOpenApiPaths } from "../externalPaths-SyPF2tgK.mjs";
|
|
6
|
+
import { FastifyPluginAsync } from "fastify";
|
|
7
|
+
|
|
8
|
+
//#region src/docs/openapi.d.ts
|
|
9
|
+
interface OpenApiOptions {
|
|
10
|
+
/** API title */
|
|
11
|
+
title?: string;
|
|
12
|
+
/** API version */
|
|
13
|
+
version?: string;
|
|
14
|
+
/** API description */
|
|
15
|
+
description?: string;
|
|
16
|
+
/** Server URL */
|
|
17
|
+
serverUrl?: string;
|
|
18
|
+
/** Route prefix for spec endpoint (default: '/_docs') */
|
|
19
|
+
prefix?: string;
|
|
20
|
+
/** API prefix for all resource paths (e.g., '/api/v1') */
|
|
21
|
+
apiPrefix?: string;
|
|
22
|
+
/** Auth roles required to access spec (default: [] = public) */
|
|
23
|
+
authRoles?: string[];
|
|
24
|
+
/** Include internal routes (default: false) */
|
|
25
|
+
includeInternal?: boolean;
|
|
26
|
+
/** Custom OpenAPI extensions */
|
|
27
|
+
extensions?: Record<string, unknown>;
|
|
28
|
+
}
|
|
29
|
+
interface OpenApiSpec {
|
|
30
|
+
openapi: string;
|
|
31
|
+
info: {
|
|
32
|
+
title: string;
|
|
33
|
+
version: string;
|
|
34
|
+
description?: string;
|
|
35
|
+
};
|
|
36
|
+
servers?: Array<{
|
|
37
|
+
url: string;
|
|
38
|
+
description?: string;
|
|
39
|
+
}>;
|
|
40
|
+
paths: Record<string, PathItem>;
|
|
41
|
+
components: {
|
|
42
|
+
schemas: Record<string, SchemaObject>;
|
|
43
|
+
securitySchemes?: Record<string, SecurityScheme>;
|
|
44
|
+
};
|
|
45
|
+
tags: Array<{
|
|
46
|
+
name: string;
|
|
47
|
+
description?: string;
|
|
48
|
+
}>;
|
|
49
|
+
security?: Array<Record<string, string[]>>;
|
|
50
|
+
}
|
|
51
|
+
interface OpenApiBuildOptions {
|
|
52
|
+
title?: string;
|
|
53
|
+
version?: string;
|
|
54
|
+
description?: string;
|
|
55
|
+
serverUrl?: string;
|
|
56
|
+
apiPrefix?: string;
|
|
57
|
+
}
|
|
58
|
+
interface PathItem {
|
|
59
|
+
get?: Operation;
|
|
60
|
+
post?: Operation;
|
|
61
|
+
put?: Operation;
|
|
62
|
+
patch?: Operation;
|
|
63
|
+
delete?: Operation;
|
|
64
|
+
options?: Operation;
|
|
65
|
+
head?: Operation;
|
|
66
|
+
}
|
|
67
|
+
interface Operation {
|
|
68
|
+
tags: string[];
|
|
69
|
+
summary: string;
|
|
70
|
+
description?: string;
|
|
71
|
+
operationId: string;
|
|
72
|
+
parameters?: Parameter[];
|
|
73
|
+
requestBody?: RequestBody;
|
|
74
|
+
responses: Record<string, Response>;
|
|
75
|
+
security?: Array<Record<string, string[]>>;
|
|
76
|
+
/** Arc permission metadata (OpenAPI extension) */
|
|
77
|
+
'x-arc-permission'?: {
|
|
78
|
+
type: string;
|
|
79
|
+
roles?: readonly string[];
|
|
80
|
+
};
|
|
81
|
+
/** Arc pipeline steps (OpenAPI extension) */
|
|
82
|
+
'x-arc-pipeline'?: Array<{
|
|
83
|
+
type: string;
|
|
84
|
+
name: string;
|
|
85
|
+
}>;
|
|
86
|
+
}
|
|
87
|
+
interface Parameter {
|
|
88
|
+
name: string;
|
|
89
|
+
in: 'path' | 'query' | 'header';
|
|
90
|
+
required?: boolean;
|
|
91
|
+
schema: SchemaObject;
|
|
92
|
+
description?: string;
|
|
93
|
+
}
|
|
94
|
+
interface RequestBody {
|
|
95
|
+
required?: boolean;
|
|
96
|
+
content: Record<string, {
|
|
97
|
+
schema: SchemaObject;
|
|
98
|
+
}>;
|
|
99
|
+
}
|
|
100
|
+
interface Response {
|
|
101
|
+
description: string;
|
|
102
|
+
content?: Record<string, {
|
|
103
|
+
schema: SchemaObject;
|
|
104
|
+
}>;
|
|
105
|
+
}
|
|
106
|
+
interface SchemaObject {
|
|
107
|
+
type?: string;
|
|
108
|
+
format?: string;
|
|
109
|
+
properties?: Record<string, SchemaObject>;
|
|
110
|
+
items?: SchemaObject;
|
|
111
|
+
required?: string[];
|
|
112
|
+
$ref?: string;
|
|
113
|
+
description?: string;
|
|
114
|
+
example?: unknown;
|
|
115
|
+
additionalProperties?: boolean | SchemaObject;
|
|
116
|
+
enum?: string[];
|
|
117
|
+
minimum?: number;
|
|
118
|
+
maximum?: number;
|
|
119
|
+
minLength?: number;
|
|
120
|
+
maxLength?: number;
|
|
121
|
+
pattern?: string;
|
|
122
|
+
}
|
|
123
|
+
interface SecurityScheme {
|
|
124
|
+
type: string;
|
|
125
|
+
scheme?: string;
|
|
126
|
+
bearerFormat?: string;
|
|
127
|
+
in?: string;
|
|
128
|
+
name?: string;
|
|
129
|
+
}
|
|
130
|
+
declare const openApiPlugin: FastifyPluginAsync<OpenApiOptions>;
|
|
131
|
+
/**
|
|
132
|
+
* Build OpenAPI spec from registry resources.
|
|
133
|
+
* Shared by HTTP docs endpoint and CLI export command.
|
|
134
|
+
*/
|
|
135
|
+
declare function buildOpenApiSpec(resources: RegistryEntry[], options?: OpenApiBuildOptions, externalPaths?: ExternalOpenApiPaths[]): OpenApiSpec;
|
|
136
|
+
declare const _default: FastifyPluginAsync<OpenApiOptions>;
|
|
137
|
+
//#endregion
|
|
138
|
+
//#region src/docs/scalar.d.ts
|
|
139
|
+
interface ScalarOptions {
|
|
140
|
+
/** Route prefix for UI (default: '/docs') */
|
|
141
|
+
routePrefix?: string;
|
|
142
|
+
/** OpenAPI spec URL (default: '/_docs/openapi.json') */
|
|
143
|
+
specUrl?: string;
|
|
144
|
+
/** Page title */
|
|
145
|
+
title?: string;
|
|
146
|
+
/** Theme (default: 'default') */
|
|
147
|
+
theme?: 'default' | 'alternate' | 'moon' | 'purple' | 'solarized' | 'bluePlanet' | 'saturn' | 'kepler' | 'mars' | 'deepSpace';
|
|
148
|
+
/** Show sidebar (default: true) */
|
|
149
|
+
showSidebar?: boolean;
|
|
150
|
+
/** Dark mode (default: false) */
|
|
151
|
+
darkMode?: boolean;
|
|
152
|
+
/** Auth roles required to access docs */
|
|
153
|
+
authRoles?: string[];
|
|
154
|
+
/** Custom CSS */
|
|
155
|
+
customCss?: string;
|
|
156
|
+
/** Favicon URL */
|
|
157
|
+
favicon?: string;
|
|
158
|
+
}
|
|
159
|
+
declare const scalarPlugin: FastifyPluginAsync<ScalarOptions>;
|
|
160
|
+
declare const _default$1: FastifyPluginAsync<ScalarOptions>;
|
|
161
|
+
//#endregion
|
|
162
|
+
export { type ExternalOpenApiPaths, type OpenApiBuildOptions, type OpenApiOptions, type OpenApiSpec, type ScalarOptions, buildOpenApiSpec, _default as openApiPlugin, openApiPlugin as openApiPluginFn, _default$1 as scalarPlugin, scalarPlugin as scalarPluginFn };
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { t as getUserRoles } from "../types-DelU6kln.mjs";
|
|
2
|
+
import { n as openApiPlugin, r as openapi_default, t as buildOpenApiSpec } from "../openapi-9nB_kiuR.mjs";
|
|
3
|
+
import fp from "fastify-plugin";
|
|
4
|
+
|
|
5
|
+
//#region src/docs/scalar.ts
|
|
6
|
+
/**
|
|
7
|
+
* Scalar API Reference Plugin
|
|
8
|
+
*
|
|
9
|
+
* Beautiful, modern API documentation UI.
|
|
10
|
+
* Lighter and more modern than Swagger UI.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* import { scalarPlugin } from '@classytic/arc/docs';
|
|
14
|
+
*
|
|
15
|
+
* await fastify.register(scalarPlugin, {
|
|
16
|
+
* routePrefix: '/docs',
|
|
17
|
+
* specUrl: '/_docs/openapi.json',
|
|
18
|
+
* });
|
|
19
|
+
*
|
|
20
|
+
* // UI available at /docs
|
|
21
|
+
*/
|
|
22
|
+
const scalarPlugin = async (fastify, opts = {}) => {
|
|
23
|
+
const { routePrefix = "/docs", specUrl = "/_docs/openapi.json", title = "API Documentation", theme = "default", showSidebar = true, darkMode = false, authRoles = [], customCss = "", favicon } = opts;
|
|
24
|
+
const scalarConfig = JSON.stringify({
|
|
25
|
+
spec: { url: specUrl },
|
|
26
|
+
theme,
|
|
27
|
+
showSidebar,
|
|
28
|
+
darkMode,
|
|
29
|
+
...favicon && { favicon }
|
|
30
|
+
});
|
|
31
|
+
const html = `<!DOCTYPE html>
|
|
32
|
+
<html>
|
|
33
|
+
<head>
|
|
34
|
+
<meta charset="utf-8">
|
|
35
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
36
|
+
<title>${title}</title>
|
|
37
|
+
${favicon ? `<link rel="icon" href="${favicon}">` : ""}
|
|
38
|
+
<style>
|
|
39
|
+
body { margin: 0; padding: 0; }
|
|
40
|
+
${customCss}
|
|
41
|
+
</style>
|
|
42
|
+
</head>
|
|
43
|
+
<body>
|
|
44
|
+
<script id="api-reference" data-url="${specUrl}"><\/script>
|
|
45
|
+
<script>
|
|
46
|
+
var configuration = ${scalarConfig};
|
|
47
|
+
document.getElementById('api-reference').dataset.configuration = JSON.stringify(configuration);
|
|
48
|
+
<\/script>
|
|
49
|
+
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"><\/script>
|
|
50
|
+
</body>
|
|
51
|
+
</html>`;
|
|
52
|
+
fastify.get(routePrefix, async (request, reply) => {
|
|
53
|
+
if (authRoles.length > 0) {
|
|
54
|
+
const user = request.user;
|
|
55
|
+
const roles = getUserRoles(user);
|
|
56
|
+
if (!authRoles.some((r) => roles.includes(r)) && !roles.includes("superadmin")) {
|
|
57
|
+
reply.code(403).send({ error: "Access denied" });
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
reply.type("text/html").send(html);
|
|
62
|
+
});
|
|
63
|
+
if (!routePrefix.endsWith("/")) fastify.get(`${routePrefix}/`, async (_, reply) => {
|
|
64
|
+
reply.redirect(routePrefix);
|
|
65
|
+
});
|
|
66
|
+
fastify.log?.debug?.(`Scalar API docs available at ${routePrefix}`);
|
|
67
|
+
};
|
|
68
|
+
var scalar_default = fp(scalarPlugin, {
|
|
69
|
+
name: "arc-scalar",
|
|
70
|
+
fastify: "5.x"
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
//#endregion
|
|
74
|
+
export { buildOpenApiSpec, openapi_default as openApiPlugin, openApiPlugin as openApiPluginFn, scalar_default as scalarPlugin, scalarPlugin as scalarPluginFn };
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { FastifyPluginAsync, FastifyRequest } from "fastify";
|
|
2
|
+
|
|
3
|
+
//#region src/scope/types.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Request Scope — The One Standard
|
|
6
|
+
*
|
|
7
|
+
* Discriminated union representing the access context of every request.
|
|
8
|
+
* Replaces scattered orgScope/orgRoles/organizationId/bypassRoles.
|
|
9
|
+
*
|
|
10
|
+
* Set once by auth adapters, read everywhere by permissions/presets/guards.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* // In a permission check
|
|
15
|
+
* const scope = request.scope;
|
|
16
|
+
* if (isElevated(scope)) return true;
|
|
17
|
+
* if (isMember(scope) && scope.orgRoles.includes('admin')) return true;
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Request scope — 4 kinds, 4 states, no ambiguity.
|
|
22
|
+
*
|
|
23
|
+
* | Kind | Meaning |
|
|
24
|
+
* |---------------|-----------------------------------|
|
|
25
|
+
* | public | No authentication |
|
|
26
|
+
* | authenticated | Logged in, no org context |
|
|
27
|
+
* | member | In an org with specific roles |
|
|
28
|
+
* | elevated | Platform admin, explicit elevation |
|
|
29
|
+
*/
|
|
30
|
+
type RequestScope = {
|
|
31
|
+
kind: 'public';
|
|
32
|
+
} | {
|
|
33
|
+
kind: 'authenticated';
|
|
34
|
+
} | {
|
|
35
|
+
kind: 'member';
|
|
36
|
+
organizationId: string;
|
|
37
|
+
orgRoles: string[];
|
|
38
|
+
teamId?: string;
|
|
39
|
+
} | {
|
|
40
|
+
kind: 'elevated';
|
|
41
|
+
organizationId?: string;
|
|
42
|
+
elevatedBy: string;
|
|
43
|
+
};
|
|
44
|
+
/** Check if scope is `member` kind */
|
|
45
|
+
declare function isMember(scope: RequestScope): scope is Extract<RequestScope, {
|
|
46
|
+
kind: 'member';
|
|
47
|
+
}>;
|
|
48
|
+
/** Check if scope is `elevated` kind */
|
|
49
|
+
declare function isElevated(scope: RequestScope): scope is Extract<RequestScope, {
|
|
50
|
+
kind: 'elevated';
|
|
51
|
+
}>;
|
|
52
|
+
/** Check if scope has org access (member OR elevated) */
|
|
53
|
+
declare function hasOrgAccess(scope: RequestScope): boolean;
|
|
54
|
+
/** Check if request is authenticated (any kind except public) */
|
|
55
|
+
declare function isAuthenticated(scope: RequestScope): boolean;
|
|
56
|
+
/** Get organizationId from scope (if present) */
|
|
57
|
+
declare function getOrgId(scope: RequestScope): string | undefined;
|
|
58
|
+
/** Get org roles from scope (empty array if not a member) */
|
|
59
|
+
declare function getOrgRoles(scope: RequestScope): string[];
|
|
60
|
+
/** Get team ID from scope (only available on member kind) */
|
|
61
|
+
declare function getTeamId(scope: RequestScope): string | undefined;
|
|
62
|
+
/** Default public scope — used as initial decoration value */
|
|
63
|
+
declare const PUBLIC_SCOPE: Readonly<RequestScope>;
|
|
64
|
+
/** Default authenticated scope — used when user is logged in but no org */
|
|
65
|
+
declare const AUTHENTICATED_SCOPE: Readonly<RequestScope>;
|
|
66
|
+
//#endregion
|
|
67
|
+
//#region src/scope/elevation.d.ts
|
|
68
|
+
interface ElevationOptions {
|
|
69
|
+
/** Roles that can use elevation (default: ['superadmin']) */
|
|
70
|
+
platformRoles?: string[];
|
|
71
|
+
/** Header name for scope declaration (default: 'x-arc-scope') */
|
|
72
|
+
scopeHeader?: string;
|
|
73
|
+
/** Header name for target organization (default: 'x-organization-id') */
|
|
74
|
+
orgHeader?: string;
|
|
75
|
+
/** Called when elevation happens — use for audit logging */
|
|
76
|
+
onElevation?: (event: ElevationEvent) => void | Promise<void>;
|
|
77
|
+
}
|
|
78
|
+
interface ElevationEvent {
|
|
79
|
+
userId: string;
|
|
80
|
+
organizationId?: string;
|
|
81
|
+
request: FastifyRequest;
|
|
82
|
+
timestamp: Date;
|
|
83
|
+
}
|
|
84
|
+
declare const elevationPlugin: FastifyPluginAsync<ElevationOptions>;
|
|
85
|
+
declare const _default: FastifyPluginAsync<ElevationOptions>;
|
|
86
|
+
//#endregion
|
|
87
|
+
export { AUTHENTICATED_SCOPE as a, getOrgId as c, hasOrgAccess as d, isAuthenticated as f, elevationPlugin as i, getOrgRoles as l, isMember as m, ElevationOptions as n, PUBLIC_SCOPE as o, isElevated as p, _default as r, RequestScope as s, ElevationEvent as t, getTeamId as u };
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { t as __exportAll } from "./chunk-C7Uep-_p.mjs";
|
|
2
|
+
import { t as getUserRoles } from "./types-DelU6kln.mjs";
|
|
3
|
+
import { t as arcLog } from "./logger-ByrvQWZO.mjs";
|
|
4
|
+
import fp from "fastify-plugin";
|
|
5
|
+
|
|
6
|
+
//#region src/scope/elevation.ts
|
|
7
|
+
/**
|
|
8
|
+
* Elevation Plugin — Explicit Platform Admin Access
|
|
9
|
+
*
|
|
10
|
+
* Opt-in Fastify plugin that allows platform admins to explicitly
|
|
11
|
+
* elevate their scope via the `x-arc-scope: platform` header.
|
|
12
|
+
*
|
|
13
|
+
* Without this header, a superadmin is treated as a normal user.
|
|
14
|
+
* This prevents implicit bypass and enables audit logging.
|
|
15
|
+
*
|
|
16
|
+
* ## Lifecycle
|
|
17
|
+
*
|
|
18
|
+
* Elevation wraps `fastify.authenticate` so it always runs AFTER
|
|
19
|
+
* authentication has set `request.user`. This avoids the `onRequest`
|
|
20
|
+
* timing issue where `request.user` doesn't exist yet.
|
|
21
|
+
*
|
|
22
|
+
* Flow: `authenticate()` → user is set → `elevation check` → scope is set
|
|
23
|
+
*
|
|
24
|
+
* Inspired by Stripe Connect's `Stripe-Account` header.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* const app = await createApp({
|
|
29
|
+
* auth: { type: 'betterAuth', betterAuth: adapter },
|
|
30
|
+
* elevation: {
|
|
31
|
+
* platformRoles: ['superadmin'],
|
|
32
|
+
* onElevation: (event) => auditLog.write(event),
|
|
33
|
+
* },
|
|
34
|
+
* });
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
var elevation_exports = /* @__PURE__ */ __exportAll({
|
|
38
|
+
default: () => elevation_default,
|
|
39
|
+
elevationPlugin: () => elevationPlugin
|
|
40
|
+
});
|
|
41
|
+
const log = arcLog("elevation");
|
|
42
|
+
const elevationPlugin = async (fastify, opts = {}) => {
|
|
43
|
+
const { platformRoles = ["superadmin"], scopeHeader = "x-arc-scope", orgHeader = "x-organization-id", onElevation } = opts;
|
|
44
|
+
if (!fastify.hasDecorator("authenticate")) {
|
|
45
|
+
log.warn("authenticate decorator not found. Register auth before elevation. Elevation will not function.");
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const originalAuthenticate = fastify.authenticate;
|
|
49
|
+
const authenticateWithElevation = async (request, reply) => {
|
|
50
|
+
await originalAuthenticate.call(fastify, request, reply);
|
|
51
|
+
if (reply.sent) return;
|
|
52
|
+
if (request.headers[scopeHeader] !== "platform") return;
|
|
53
|
+
const user = request.user;
|
|
54
|
+
if (!user) {
|
|
55
|
+
log.debug("Elevation requested but no user after auth");
|
|
56
|
+
reply.code(401).send({
|
|
57
|
+
success: false,
|
|
58
|
+
error: "Unauthorized",
|
|
59
|
+
message: "Authentication required for platform elevation",
|
|
60
|
+
code: "ELEVATION_AUTH_REQUIRED"
|
|
61
|
+
});
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const userRoles = getUserRoles(user);
|
|
65
|
+
if (!platformRoles.some((r) => userRoles.includes(r))) {
|
|
66
|
+
log.debug("Elevation rejected — insufficient roles", {
|
|
67
|
+
userId: user.id ?? user._id,
|
|
68
|
+
userRoles,
|
|
69
|
+
required: platformRoles
|
|
70
|
+
});
|
|
71
|
+
reply.code(403).send({
|
|
72
|
+
success: false,
|
|
73
|
+
error: "Forbidden",
|
|
74
|
+
message: "Insufficient privileges for platform elevation",
|
|
75
|
+
code: "ELEVATION_FORBIDDEN"
|
|
76
|
+
});
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const orgId = request.headers[orgHeader];
|
|
80
|
+
const userId = String(user.id ?? user._id ?? "unknown");
|
|
81
|
+
request.scope = {
|
|
82
|
+
kind: "elevated",
|
|
83
|
+
organizationId: orgId || void 0,
|
|
84
|
+
elevatedBy: userId
|
|
85
|
+
};
|
|
86
|
+
log.debug("Scope elevated", {
|
|
87
|
+
userId,
|
|
88
|
+
organizationId: orgId
|
|
89
|
+
});
|
|
90
|
+
if (onElevation) try {
|
|
91
|
+
await onElevation({
|
|
92
|
+
userId,
|
|
93
|
+
organizationId: orgId || void 0,
|
|
94
|
+
request,
|
|
95
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
96
|
+
});
|
|
97
|
+
} catch {
|
|
98
|
+
log.warn("onElevation callback threw — continuing request");
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
fastify.authenticate = authenticateWithElevation;
|
|
102
|
+
log.debug("Plugin registered", {
|
|
103
|
+
platformRoles,
|
|
104
|
+
scopeHeader
|
|
105
|
+
});
|
|
106
|
+
};
|
|
107
|
+
var elevation_default = fp(elevationPlugin, {
|
|
108
|
+
name: "arc-elevation",
|
|
109
|
+
fastify: "5.x"
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
//#endregion
|
|
113
|
+
export { elevation_default as n, elevation_exports as r, elevationPlugin as t };
|